У меня есть код, который определяет, зависла ли очередь событий Java AWT (занята обработкой какого-либо события или ожидает блокировки) в течение чрезмерного времени из-за ошибочного внешнего кода, который не может использовать SwingWorker или аналогичный, и я хочу предложить восстановить. Простое уничтожение потока отправки событий с помощью Thread.stop работает, но это может быть опасно (и EQ может быть заблокирован просто потому, что компьютер временно перегружен), поэтому я бы предпочел запрашивать подтверждение у пользователя. Однако отображение диалога требует ожидания разблокировки эквалайзера, что как раз и невозможно.

Есть ли способ из достаточно переносимой Java-программы отображать диалоговое окно (или действительно любой элемент пользовательского интерфейса, который может реагировать на входные события) без участия обычной очереди событий?

Я уже пытался запустить SystemTray.add(TrayIcon), который может отображать элемент в трее при вызове из другого потока... но значок не рисуется, а ActionEvent не доставляются, так что это бесполезно.

Также кажется возможным отобразить новый JFrame и даже нарисовать на нем метку с помощью paintImmediately, но пока нет очевидного способа получать события мыши.

Следуя подсказке Тома Хоутина, я попытался создать новый AppContext. В JDK 6u18 диалоговое окно, отображаемое в новом контексте, выглядит правильно, но не получает события мыши, пока основная очередь событий не будет разблокирована, что противоречит цели:

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.WindowConstants;
import sun.awt.AppContext;
import sun.awt.SunToolkit;
public class Main {
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public @Override void run() {
                new MainWindow().setVisible(true);
                System.err.println("main context: " + AppContext.getAppContext());
            }
        });
        new TrackEQ(1000*3);
    }
    private static class MainWindow extends JFrame {
        MainWindow() {
            setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            JButton pause = new JButton("Pause");
            pause.addActionListener(new ActionListener() {
                public @Override void actionPerformed(ActionEvent e) {
                    try {
                        Thread.sleep(15000);
                    } catch (InterruptedException x) {
                        x.printStackTrace();
                    }
                }
            });
            getContentPane().add(pause);
            pack();
            setLocation(100, 100);
        }
    }
    private static class TrackEQ implements Runnable {
        private final ScheduledExecutorService svc;
        private final int timeout;
        private boolean stuck = false;
        private boolean wait = false;
        private Thread eq;
        TrackEQ(int timeout) {
            this.timeout = timeout;
            svc = Executors.newSingleThreadScheduledExecutor();
            svc.schedule(this, 0, TimeUnit.MILLISECONDS);
        }
        public @Override synchronized void run() {
            if (EventQueue.isDispatchThread()) {
                stuck = false;
                eq = Thread.currentThread();
            } else {
                if (stuck && !wait) {
                    System.err.println("UI is stuck!");
                    wait = true;
                    Map<Thread,StackTraceElement[]> stackTraces = Thread.getAllStackTraces();
                    StackTraceElement[] stack = stackTraces.get(eq);
                    if (stack != null) {
                        for (StackTraceElement el : stack) {
                            System.err.println("stuck at " + el);
                        }
                        ThreadGroup grp = new ThreadGroup("showing dialog");
                        grp.setDaemon(true);
                        new Thread(grp, new Runnable() {
                            public @Override void run() {
                                System.err.println("created new app context in " + Thread.currentThread().getThreadGroup());
                                SunToolkit.createNewAppContext();
                                EventQueue.invokeLater(new Runnable() {
                                    public @Override void run() {
                                        System.err.println("main EQ=" + eq + " whereas my EQ=" + Thread.currentThread());
                                        System.err.println("will show dialog in " + AppContext.getAppContext());
                                        final JDialog dlg = new JDialog();
                                        dlg.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
                                        JButton fix = new JButton("Fix!");
                                        fix.addActionListener(new ActionListener() {
                                            @SuppressWarnings("deprecation")
                                            public @Override void actionPerformed(ActionEvent e) {
                                                System.err.println("agreed to fix");
                                                eq.stop();
                                                wait = false;
                                                dlg.setVisible(false);
                                            }
                                        });
                                        dlg.getContentPane().add(fix);
                                        dlg.pack();
                                        dlg.setLocation(200, 100);
                                        dlg.setVisible(true);
                                        System.err.println("showed dialog");
                                    }
                                });
                            }
                        }, "showing dialog").start();
                    } else {
                        System.err.println("no stack trace for " + eq + "; listed threads: " + stackTraces.keySet());
                    }
                } else {
                    stuck = true;
                }
                EventQueue.invokeLater(this);
                svc.schedule(this, timeout, TimeUnit.MILLISECONDS);
            }
        }
    }
    private Main() {}
}
4
Jesse Glick 18 Янв 2010 в 00:18
4
Правильное решение — не писать код, который блокирует очередь событий. Это должно убить поток и сбросить память в среде отладки и ничего не делать в рабочей среде.
 – 
Anon.
18 Янв 2010 в 00:23
2
Вместо того, чтобы пытаться перепроектировать механизм рисования AWT, было бы лучше использовать эквалайзер соответствующим образом — передавать длинные процессы другим потокам и избегать операций блокировки.
 – 
akf
18 Янв 2010 в 00:24
Присоединяюсь к предыдущим 2 комментариям. В очереди событий или их обработке нет ничего плохого, если только они не вызваны ошибками в кодировании приложения.
 – 
Carl Smotricz
18 Янв 2010 в 00:27
Конечно нежелательно блокировать очередь событий. Вопрос был о том, как восстановить, когда это сделал чужой код, и этот код еще не исправлен.
 – 
Jesse Glick
19 Янв 2010 в 14:52

2 ответа

API-интерфейсы AWT написаны так, как если бы существовала только очередь событий. Итак, мы говорим об изменяемой статике и, следовательно, о плохом дизайне и злых хаках.

Подключаемый модуль Sun и WebStart используют недокументированный API для взлома контекста. Ознакомьтесь с классом AppContext. Сначала контекст ищется с помощью ThreadGroup и, если это неубедительно, затем путем проверки ClassLoader в стеке.

Очевидные комментарии: вы, конечно, можете запустить отдельный процесс. Большинство приложений просто следят за тем, чтобы они не блокировали важные ресурсы, включая EDT.

2
Tom Hawtin - tackline 18 Янв 2010 в 03:43
Это похоже на правильное направление; AppContext — это то, что я искал, и код для AppletClassLoader и SunToolkit выглядит как правильная модель. (Очевидно, что этот код будет специфичным для Sun JRE, поэтому портативное приложение должно включать какой-то запасной вариант — отключение эквалайзера без запроса.) К сожалению, он не работает полностью: диалоговое окно, показанное в альтернативном контексте приложения, рисуется нормально. но не получает никаких входных событий, пока не возобновится очередь основных событий.
 – 
Jesse Glick
23 Янв 2010 в 20:50

Класс SwingWorker доступен в некоторых версиях Java.

Это позволяет вам запускать задачи, потребляющие три раза, в отдельном потоке.

В зависимости от того, какую версию JRE вы используете, вы можете подключить класс для запуска существующих задач в отдельных потоках.

Эти примеры взяты из Википедии:

До Java 6:

SwingWorker worker = new SwingWorker() {
    public Object construct() {
        ... //add the code for the background thread
    }
    public void finished() {
    ... //code that you add here will run in the UI thread
    }

};

Или для Java 6:

SwingWorker worker = new SwingWorker<Document, Void>() {
  public Document doInBackground() {
      Document intDoc = loadXML();
      return intDoc;
  }
};
0
martinr 18 Янв 2010 в 00:33
1
Бэкпорт SwingWorker совместим с текущей версией: swingworker.dev.java.net
 – 
trashgod
18 Янв 2010 в 01:53
Я знаю о SwingWorker, но вопрос был не в этом.
 – 
Jesse Glick
19 Янв 2010 в 14:52