С помощью людей на stackoverflow я смог получить следующий рабочий код простого обратного отсчета GUI (который просто отображает окно, считающее в обратном порядке секунды). Моя основная проблема с этим кодом invokeLater
материал.
Насколько я понимаю invokeLater
, это отправляет задачу в поток диспетчеризации события (EDT), и затем EDT выполняет эту задачу каждый раз, когда это "может" (независимо от того, что это означает). Это правильно?
К моему пониманию код работает как это:
В main
метод мы используем invokeLater
показать окно (showGUI
метод). Другими словами, код, отображающий окно, будет выполнен в EDT.
В main
метод мы также запускаем counter
и счетчик (конструкцией) выполняется в другом потоке (таким образом, это в конечном счете не диспетчеризирует поток).Правильно?
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();
}
});
}
}
Если я правильно понял ваш вопрос, вы задаетесь вопросом, почему вы не можете этого сделать:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
showGUI();
}
});
counter.start();
}
Причина, по которой вы не можете этого сделать, заключается в том, что планировщик не дает никаких гарантий ... только потому, что вы вызвали showGUI ()
, а затем вы вызвали counter.start ()
, это не означает, что код в showGUI ()
будет выполняться перед кодом в методе выполнения счетчика
.
Подумайте об этом так:
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 не являются «потокобезопасными»: их вызов из нескольких потоков создает риск интерференции потоков или ошибок согласованности памяти. 1Q: Итак, если у нас есть графический интерфейс, почему мы должны запускать его в отдельном потоке?
A: Вероятно, есть лучший ответ, чем мой, но если вы хотите чтобы обновить графический интерфейс из EDT (что вы делаете), вам нужно запустить его из EDT.Q: И почему мы не можем просто запустить поток, как любой другой поток?
A: См. Предыдущий ответ.Q: Почему мы используем invokeLater и почему этот поток (EDT) начинает выполнять запрос, когда он готов. Почему он не всегда готов?
A: EDT может иметь некоторые другие события AWT, которые он должен обработать.invokeLater
Вызывает асинхронное выполнение doRun.run () в потоке диспетчеризации событий AWT. Это произойдет после обработки всех ожидающих событий AWT. Этот метод следует использовать, когда потоку приложения необходимо обновить графический интерфейс. 2
Фактически вы запускаете поток счетчика
из EDT. Если вы вызвали counter.start ()
после блока invokeLater
, счетчик, скорее всего, начнет работать до того, как станет видимым графический интерфейс. Теперь, поскольку вы создаете графический интерфейс в EDT, он не будет существовать , когда счетчик
начнет его обновлять. К счастью, вы, кажется, пересылаете обновления графического интерфейса в EDT, что правильно, и поскольку EventQueue представляет собой очередь, первое обновление произойдет после создания графического интерфейса, поэтому не должно быть причин, по которым это не сработает. Но какой смысл обновлять графический интерфейс, который может быть еще не виден?
Что такое 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 (...)
.
Я считаю, что это придирки, но все же важно увидеть вопрос.