Как событие диспетчеризирует работу потока?

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

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

К моему пониманию код работает как это:

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

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

  3. counter выполняется в отдельном потоке, и периодически он звонит updateGUI. updateGUI как предполагается, обновляет GUI. И GUI работает в 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();
            }
        });
    }

}

12
задан Community 23 May 2017 в 12:22
поделиться

3 ответа

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

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

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

Подумайте об этом так:

  • invokeLater запускает поток, и этот поток планирует асинхронное событие в EDT, которому поручено создать JLabel .
  • счетчик - это отдельный поток, который зависит от существования JLabel , поэтому он может вызывать label.setText («У вас есть» + i + «секунды.»);

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

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

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

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

Обновление:

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

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

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

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

16
ответ дан 2 December 2019 в 18:52
поделиться

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

2
ответ дан 2 December 2019 в 18:52
поделиться

Что такое EDT?

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

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

Затем некоторые придирки:

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

А затем:

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

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

Я считаю, что это придирки, но все же важно увидеть вопрос.

1
ответ дан 2 December 2019 в 18:52
поделиться
Другие вопросы по тегам:

Похожие вопросы: