在人们的帮助下 stackoverflow我能够获得以下简单 GUI 倒计时的工作代码(它只显示一个倒计时秒数的窗口)。这段代码的主要问题是 invokeLater
内容。
据我所知,invokeLater
将任务发送到事件分派(dispatch)线程 (EDT),然后 EDT 在“可以”时执行此任务(无论这意味着什么)。 是这样吗?
据我了解,代码是这样工作的:
在
main
方法中,我们使用invokeLater
来显示窗口(showGUI
方法)。换句话说,显示窗口的代码将在 EDT 中执行。在
main
方法中,我们还启动了counter
并且计数器(通过构造)在另一个线程中执行(因此它不在事件调度中线)。对吧?counter
在单独的线程中执行,并定期调用updateGUI
。updateGUI
应该更新 GUI。 GUI 在 EDT 中运行。因此,updateGUI
也应该在 EDT 中执行。这就是为什么updateGUI
的代码包含在invokeLater
中的原因。是吗?
我不清楚的是为什么我们从 EDT 调用 counter
。无论如何,它不在 EDT 中执行。它立即启动,一个新线程和 counter
在那里执行。那么,为什么我们不能在 invokeLater
block 之后调用 main 方法中的 counter
呢?
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()
中的代码将在 counter
的 run 方法中的代码之前执行。
这样想:
- 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
将在之前创建计数器
启动。
更新:
Q: And I do not understand what is special about this thread.
A: Swing event handling code runs on a special thread known as the event dispatch thread. Most code that invokes Swing methods also runs on this thread. This is necessary because most Swing object methods are not "thread safe": invoking them from multiple threads risks thread interference or memory consistency errors. 1Q: So, if we have a GUI why should we start it in a separate thread?
A: There is probably a better answer than mine, but if you want to update the GUI from the EDT (which you do), then you have to start it from the EDT.Q: And why we cannot just start the thread like any other other thread?
A: See previous answer.Q: Why we use some invokeLater and why this thread (EDT) start to execute request when it's ready. Why it is not always ready?
A: The EDT might have some other AWT events it has to process.invokeLater
Causes doRun.run() to be executed asynchronously on the AWT event dispatching thread. This will happen after all pending AWT events have been processed. This method should be used when an application thread needs to update the GUI. 2
关于java - 事件派发线程如何工作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2484425/