Как заменить или перезапустить заблокированный Swing EventDispatchThread/EventQueue в настольном приложении Java 8
Некоторое время назад мы добавили в наше приложение некоторый код для обнаружения и попытки восстановления после тупика Swing EDT, чтобы пользователь мог хотя бы сохранить свои файлы (было бы лучше не иметь тупиков, но...). В Java 1.6 это легко. Определите, что EDT был заблокирован на достаточное количество времени, и затем вызовите это из фонового потока:
EventQueue newQ = new EventQueue();
Toolkit.getDefaultToolkit().getSystemEventQueue().push(newQ);
Новые события пользовательского интерфейса будут обрабатываться в новом EventQueue/EDT, и пользователь может сохранить свою работу.
В Java 8 это не работает, потому что реализация EventQueue.push была изменена для копирования (заблокированного) EventDispatchThread из старой очереди в новую.
1 ответ
Конечно, я всегда могу сделать что-то немного злое
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);
}
}
Это запускает новый EventDispatchThread, позволяющий использовать пользовательский интерфейс приложения. Я был в состоянии сохранить данные, как если бы я был пользователем. Я не уверен, какие недостатки могут быть. Может быть, есть менее страшный способ перезапустить заблокированный EDT?
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();
}
Это все еще не очень хорошо, но это работает.
initDispatchThread
не нужно вызывать вручную, так как EventQueue делает это автоматически, когда dispatchThread
нулевой.
Если старый поток не остановлен и он разблокируется позже, все сходит с ума. Полагаю, тогда у нас есть два потока, обрабатывающих Очередь, и здесь нет ничего, что строит для безопасности потоков, поэтому все это ломается.
Я все еще ищу лучшее решение для этого.
У меня была еще одна идея - создать свой собственный EventQueue и заменить его оригинальным, используя EventQueue.push(newQueue)
Но, глядя на код EventQueue, он может быть расширен, но не изменен так, как это необходимо. И переписать это выглядит также проблематично для меня - там происходит много сложных вещей, с которыми я не хочу связываться.
// 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
Это решение не очень красивое, но оно работает. И это работает без размышлений и setAccessible(true)
,
Я использую одну деталь из push()
способ скопировать вновь созданный поток из newEventQueue1
в newEventQueue2
, который унаследовал все от оригинала systemEventQueue
,
После запуска нового потока и установки очереди старый поток NEEEDS должен быть прекращен. Если это не так - если он разблокируется, он продолжит обрабатывать очередь, а затем он станет грязным. Система не готова к параллельной обработке двумя потоками.