Как работает поток рассылки событий?

С помощью людей из stackru я смог получить следующий рабочий код простого обратного отсчета GUI (который просто отображает окно, отсчитывающее секунды). Моя главная проблема с этим кодом invokeLater вещи.

Насколько я понимаю invokeLater он отправляет задачу в поток диспетчеризации событий (EDT), а затем EDT выполняет эту задачу всякий раз, когда это "возможно" (что бы это ни значило). Это правильно?

Насколько я понимаю, код работает так:

  1. в main метод, который мы используем invokeLater показать окно (showGUI метод). Другими словами, код, отображающий окно, будет выполнен в EDT.

  2. в main Метод мы также начинаем counter и счетчик (по построению) выполняется в другом потоке (поэтому он не находится в потоке диспетчеризации событий). Правильно?

  3. counter выполняется в отдельном потоке и периодически вызывает updateGUI, updateGUI предполагается обновить графический интерфейс. И графический интерфейс работает в EDT. Так, updateGUI также должны быть выполнены в EDT. Это причина, почему код для updateGUI заключен в invokeLater, Это правильно?

Что мне не понятно, так это то, почему мы называем counter из EDT. Во всяком случае, это не выполняется в EDT. Это начинается немедленно, новая тема и counter выполняется там. Итак, почему мы не можем назвать counter в основном методе после invokeLater блок?

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;

public class CountdownNew {

    static JLabel label;

    // Method which defines the appearance of the window.   
    public static void showGUI() {
        JFrame frame = new JFrame("Simple Countdown");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        label = new JLabel("Some Text");
        frame.add(label);
        frame.pack();
        frame.setVisible(true);
    }

    // Define a new thread in which the countdown is counting down.
    public static Thread counter = new Thread() {
        public void run() {
            for (int i=10; i>0; i=i-1) {
                updateGUI(i,label);
                try {Thread.sleep(1000);} catch(InterruptedException e) {};
            }
        }
    };

    // A method which updates GUI (sets a new value of JLabel).
    private static void updateGUI(final int i, final JLabel label) {
        SwingUtilities.invokeLater( 
            new Runnable() {
                public void run() {
                    label.setText("You have " + i + " seconds.");
                }
            }
        );
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                showGUI();
                counter.start();
            }
        });
    }

}

4 ответа

Решение

Если я правильно понимаю ваш вопрос, вы удивляетесь, почему вы не можете сделать это:

public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            showGUI();
        }
    });
    counter.start();
}

Причина, по которой вы не можете этого сделать, заключается в том, что планировщик не дает никаких гарантий... только потому, что вы вызвали showGUI() а потом ты призвал counter.start() не означает, что код в showGUI() будет выполнен перед кодом в методе запуска counter,

Думайте об этом так:

  • invokeLater запускает поток, и этот поток планирует асинхронное событие в EDT, задачей которого является создание JLabel,
  • счетчик представляет собой отдельный поток, который зависит от JLabel существует, чтобы он мог вызвать label.setText("You have " + i + " seconds.");

Теперь у вас есть состояние гонки: JLabel должен быть создан ДО counter поток запускается, если он не создан до запуска потока счетчика, то ваш поток счетчика будет вызывать setText на неинициализированном объекте.

Чтобы гарантировать, что условие гонки устранено, мы должны гарантировать порядок выполнения, и один из способов гарантировать это - выполнить showGUI() а также counter.start() последовательно в том же потоке:

public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            showGUI();
            counter.start();
        }
    });
}

Сейчас showGUI(); а также counter.start(); выполняются из одного потока, таким образом, JLabel будет создан до counter запущен

Обновить:

Q: И я не понимаю, что особенного в этой теме.
A: Код обработки событий Swing выполняется в специальном потоке, известном как поток диспетчеризации событий. Большая часть кода, который вызывает методы Swing, также выполняется в этом потоке. Это необходимо, поскольку большинство методов объекта Swing не являются "поточно-ориентированными": их вызов из нескольких потоков может привести к помехам потоков или ошибкам согласованности памяти. 1

Q: Итак, если у нас есть графический интерфейс, почему мы должны запускать его в отдельном потоке?
A: Возможно, есть лучший ответ, чем мой, но если вы хотите обновить GUI из EDT (что вы делаете), то вы должны запустить его из EDT.

Q: И почему мы не можем просто запустить поток, как любой другой поток?
A: Смотрите предыдущий ответ.

Q: Почему мы используем некоторый invokeLater и почему этот поток (EDT) начинает выполнять запрос, когда он готов. Почему это не всегда готово?
A: У EDT могут быть другие события AWT, которые он должен обработать. invokeLater Заставляет doRun.run() выполняться асинхронно в потоке диспетчеризации событий AWT. Это произойдет после обработки всех ожидающих событий AWT. Этот метод следует использовать, когда поток приложения должен обновить графический интерфейс. 2

Вы на самом деле начинаете counter нить от EDT. Если вы позвонили counter.start() после invokeLater блок, счетчик, скорее всего, начнет работать, прежде чем графический интерфейс станет видимым. Теперь, поскольку вы создаете GUI в EDT, GUI не будет существовать, когда counter начинает обновлять его. К счастью, вы, кажется, пересылаете обновления GUI в EDT, что является правильным, и, поскольку EventQueue является очередью, первое обновление произойдет после того, как GUI был создан, поэтому не должно быть никаких причин, почему это не будет работать. Но какой смысл обновлять графический интерфейс, который еще не виден?

Что такое EDT?

Это обходной путь для решения многих проблем параллелизма, которые есть в Swing API;)

Серьезно, многие компоненты Swing не являются "потокобезопасными" (некоторые известные программисты даже называли Swing "враждебным потоком"). Наличие уникального потока, в котором все обновления выполняются для компонентов, враждебных по отношению к потоку, позволяет избежать многих потенциальных проблем параллелизма. В дополнение к этому, вы также гарантируете, что он запустит Runnable что вы проходите через это, используя invokeLater в последовательном порядке.

Тогда немного придирки:

public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            showGUI();
            counter.start();
        }
    });
}

А потом:

В основном методе мы также запускаем счетчик, и счетчик (по построению) выполняется в другом потоке (поэтому он не находится в потоке диспетчеризации событий). Правильно?

Вы не запускаете счетчик в основном методе. Вы запускаете счетчик в методе run() анонимного Runnable это выполняется на EDT. Таким образом, вы действительно начинаете счетчик Thread из EDT, а не основной метод. Тогда, потому что это отдельный поток, он не запускается на EDT. Но счетчик определенно запускается на EDT, а не в Thread выполнение main(...) метод.

Это придирчиво, но все же важно увидеть вопрос, который я думаю.

Это просто, это так

Шаг 1 . Начальный поток, также называемый основным потоком, создан.

Шаг 2. Создайте работающий объект и передайте его invokeLate().

Шаг 3. Это инициализирует GUI, но не создает GUI.

Шаг 4. InvokeLater() планирует созданный объект для выполнения на EDT.

Шаг 5. Графический интерфейс создан.

Шаг 6. Все происходящие события будут помещены в EDT.

Другие вопросы по тегам