Как работает поток рассылки событий?
С помощью людей из stackru я смог получить следующий рабочий код простого обратного отсчета GUI (который просто отображает окно, отсчитывающее секунды). Моя главная проблема с этим кодом invokeLater
вещи.
Насколько я понимаю invokeLater
он отправляет задачу в поток диспетчеризации событий (EDT), а затем EDT выполняет эту задачу всякий раз, когда это "возможно" (что бы это ни значило). Это правильно?
Насколько я понимаю, код работает так:
в
main
метод, который мы используемinvokeLater
показать окно (showGUI
метод). Другими словами, код, отображающий окно, будет выполнен в EDT.в
main
Метод мы также начинаемcounter
и счетчик (по построению) выполняется в другом потоке (поэтому он не находится в потоке диспетчеризации событий). Правильно?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 не являются "поточно-ориентированными": их вызов из нескольких потоков может привести к помехам потоков или ошибкам согласованности памяти. 1Q: Итак, если у нас есть графический интерфейс, почему мы должны запускать его в отдельном потоке?
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.