How to replace or restart a deadlocked Swing Event

2019-08-11 09:51发布

A while back we added some code to our application to detect and attempt to recover from a Swing EDT deadlock, so the user could at least save their files (it would be best to not have a deadlock, but...). In Java 1.6, this is easy. Detect that the EDT has been blocked for a sufficient amount of time, and then call this from a background thread:

EventQueue newQ = new EventQueue();
Toolkit.getDefaultToolkit().getSystemEventQueue().push(newQ);

New UI events will be processed on a new EventQueue/EDT, and the user can save their work.

In Java 8, this does not work because the implementation of EventQueue.push has been changed to copy the (blocked) EventDispatchThread from the old queue to a new one.

3条回答
家丑人穷心不美
2楼-- · 2019-08-11 10:15
try {
    Field field = EventQueue.class.getDeclaredField("dispatchThread");
    field.setAccessible(true);
    Thread dispatchThread = (Thread) field.get(systemEventQueue);
    field.set(systemEventQueue, null);
    dispatchThread.stop();
} catch (Exception e) {
    e.printStackTrace();
}

It's still not very good, but it works.

initDispatchThread does not need to be manually invoked, as the EventQueue does this automatically when dispatchThread is null.

If the old thread is not stopped and it unblocks later, everything goes crazy. I suppose then we have two threads processing the Queue and nothing here is build for thread-safety thus it all breaks down.

I'm still looking for a better solution to do this.

Another idea i had is to create my own EventQueue and replace the original one with it using EventQueue.push(newQueue), but looking into the code of EventQueue it can be extended, but not modified in the way neccessary. And rewriting it looks also problematic to me - there is a lot of sophisticated stuff happening there, which i don't want to mess with.

查看更多
贪生不怕死
3楼-- · 2019-08-11 10:18
// run this when program starts to identify and remember the initial awtEventThread
Thread awtEventThread;
// identify the original thread:
EventQueue.invokeLater(() -> awtEventThread = Thread.currentThread());

// run this when a reset is neccessary:
EventQueue systemEventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue(); // the currently active Queue
EventQueue newEventQueue1 = new EventQueue(); // helper Queue to create a new AWT-Event-Threads
newEventQueue1.postEvent(new InvocationEvent(this, () -> {})); // init new AWT-Event-Thread - it happens automatically when an event is posted
EventQueue newEventQueue2 = new EventQueue(); // the new queue we want to use
systemEventQueue.push(newEventQueue2); // copy thread & events from systemEventQueue
newEventQueue1.push(newEventQueue2); // copy thread & (no) events from newEventQueue1 *HACK*
awtEventThread.stop(); // stop the old thread to prevent two threads processing the Queue - would get MESSY
EventQueue.invokeLater(() -> awtEventThread = Thread.currentThread()); // update our awtEventThread variable for the next time

This solution is not very beautiful, but it works. And it works without reflection and setAccessible(true).

I use one immplementation detail of the push() method to copy the newly created Thread from newEventQueue1 to newEventQueue2, which inherited everything from the original systemEventQueue.

After the new Thread is started and the Queue is set up the old thread NEEEDS to be terminated. If not so - in case it unblocks it will continue to process the queue and then it gets messy. The system is not ready to be processed by two threads in parallel.

查看更多
混吃等死
4楼-- · 2019-08-11 10:30

Of course, I can always do something a little evil:

private static void hackAroundJava8Protections(EventQueue newQ) {
    try {
        Field field = newQ.getClass().getDeclaredField("dispatchThread");
        field.setAccessible(true);
        field.set(newQ, null);
        Method method = newQ.getClass().getDeclaredMethod("initDispatchThread");
        method.setAccessible(true);
        method.invoke(newQ);
    } catch (Throwable e) {
        throw new RuntimeException(e);
    }
}

This starts a new EventDispatchThread, allowing use of the application UI. I was able to save data as if I were a user. I'm not sure of what downsides there might be. Maybe there's a less scary way of restarting a blocked EDT?

查看更多
登录 后发表回答