Как работает поток отправки событий? - программирование
Подтвердить что ты не робот

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

С помощью людей в qaru.site/info/373510/... мне удалось получить следующий рабочий код простого обратного отсчета графического интерфейса (который просто отображает окно, отсчитывающее секунды), Моя основная проблема с этим кодом - это материал invokeLater.

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

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

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

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

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

}
4b9b3361

Ответ 1

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

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

Причина, по которой вы не можете этого сделать, заключается в том, что планировщик не дает никаких гарантий... только потому, что вы вызывали showGUI(), а затем вы вызывали counter.start(), не означает, что код в showGUI() будет выполнен перед кодом в методе run 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: Итак, если у нас есть GUI, зачем нам запускать его в отдельном потоке?
A: Вероятно, есть лучший ответ, чем мой, но если вы хотите обновить GUI из EDT (что вы делаете), тогда вы должны запустить его из EDT.

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

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

Ответ 2

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

Ответ 3

Что такое EDT?

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

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

Затем некоторые nitpicking:

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(...).

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

Ответ 4

Это просто, это выглядит следующим образом

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

Шаг 2. Создайте исполняемый объект и передайте его в invokeLate().

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

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

Шаг 5. Создан GUI.

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