事件发送线程如何工作? [英] How does the event dispatch thread work?
问题描述
在 stackoverflow 中的人员帮助下我能够得到一个简单的GUI倒计时的下面的工作代码(它只显示一个窗口倒数秒)。我的这个代码的主要问题是 invokeLater
的东西。
据我所知 invokeLater
,它会将任务发送到事件调度线程(EDT),然后只要可以(无论什么意思),EDT执行此任务。
根据我的理解,代码的工作原理如下:
-
在
main
方法中,我们使用invokeLater
来显示窗口(showGUI
方法)。换句话说,显示窗口的代码将在EDT中执行。 -
在
main
方法我们还启动了计数器
,并且计数器(通过构造)在另一个线程中执行(因此它不在事件调度线程中)。 -
计数器
在单独的线程中执行,并定期调用updateGUI
。updateGUI
应该更新GUI。而且,GUI正在美国东部时间(EDT)工作。所以,updateGUI
也应该在EDT中执行。这就是为什么updateGUI
的代码包含在invokeLater
中。这是对的吗?
我不清楚的是为什么我们调用 / code>从EDT。无论如何,它不在EDT中执行。它立即启动,一个新的线程和
计数器
在那里执行。所以,为什么我们不能在 invokeLater
块之后的main方法中调用计数器
?
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
public class CountdownNew {
static JLabel label;
//定义窗口外观的方法。
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);
}
//定义倒数计时的新线程。
public static Thread counter = new Thread(){
public void run(){
for(int i = 10; i> 0; i = i-1){
updateGUI(I,标签);
try {Thread.sleep(1000);} catch(InterruptedException e){};
}
}
};
//更新GUI(设置JLabel的新值)的方法。
private static void updateGUI(final int i,final JLabel label){
SwingUtilities.invokeLater(
new Runnable(){
public void run(){
label .setText(你有+ i +秒。);
}
}
);
}
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()
中的代码将在计数器
的运行方法中的代码之前执行。
以这种方式想想:
- invokeLater
启动一个线程,该线程是在EDT上安排异步事件,该事件由创建JLabel
。 - 计数器是一个单独的线程,它取决于
JLabel
存在,因此它可以调用label.setText(你有+ i +秒)。 );
现在你有比赛条件: <$ c必须先创建$ c> JLabel ,如果它不是在计数器线程启动之前创建的,那么您的计数器就会在计数器
线程将在未初始化对象上调用 setText
。
为了确保消除竞争条件,我们必须保证执行顺序和单向,以确保执行 showGUI()
和 counter.start()
顺序地在同一个线程上:
$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ b showGUI();
counter.start();
}
});
}
现在 showGUI();
和 counter.start();
从同一个线程执行,因此 JLabel
将在计数器
已启动。
更新:
Q : 我不明白这个线程是什么特别的。
A: Swing事件处理代码运行在一个已知的专用线程上作为事件调度线程。大多数调用Swing方法的代码也在这个线程上运行。这是必要的,因为大多数Swing对象方法不是线程安全:从多个线程调用它们会冒着线程干扰或内存一致性错误。 1
Q: 那么,如果我们有一个GUI,为什么我们应该在一个单独的线程中启动?
A: 可能比我的更好的答案,但如果你想从EDT更新GUI(你所做的),那么你必须从EDT开始。
Q: 为什么我们不能像任何其他线程一样启动线程?
A:
Q: 为什么我们使用一些invokeLater,为什么这个线程(EDT)开始执行请求,当它是准备。为什么它不总是准备好?
A: EDT可能会有一些其他必须处理的AWT事件。
invokeLater
导致doRun.run()在AWT事件调度线程上异步执行。这将在所有待处理的AWT事件处理完毕之后发生。当应用程序线程需要更新GUI时,应使用此方法。 2
With the help of people on stackoverflow I was able to get the following working code of a simple GUI countdown (which just displays a window counting down seconds). My main problem with this code is the invokeLater
stuff.
As far as I understand invokeLater
, it sends a task to the event dispatching thread (EDT) and then the EDT executes this task whenever it "can" (whatever that means). Is that right?
To my understanding, the code works like this:
In the
main
method we useinvokeLater
to show the window (showGUI
method). In other words, the code displaying the window will be executed in the EDT.In the
main
method we also start thecounter
and the counter (by construction) is executed in another thread (so it is not in the event dispatching thread). Right?The
counter
is executed in a separate thread and periodically it callsupdateGUI
.updateGUI
is supposed to update the GUI. And the GUI is working in the EDT. So,updateGUI
should also be executed in the EDT. It is the reason why the code for theupdateGUI
is enclosed ininvokeLater
. Is that right?
What is not clear to me is why we call the counter
from the EDT. Anyway, it is not executed in the EDT. It starts immediately, a new thread and the counter
is executed there. So, why can we not call the counter
in the main method after the invokeLater
block?
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();
}
});
}
}
If I understand your question correctly you're wonder why you can't do this:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
showGUI();
}
});
counter.start();
}
The reason why you can't do it is because the scheduler makes no guarantees... just because you invoked showGUI()
and then you invoked counter.start()
doesn't mean that the code in showGUI()
will be executed before the code in the run method of the counter
.
Think of it this way:
- invokeLater
starts a thread and that thread isschedules an asynchronous event on the EDT which is tasked with creating theJLabel
. - the counter is a separate thread that depends on the
JLabel
to exists so it can calllabel.setText("You have " + i + " seconds.");
Now you have a race condition: JLabel
must be created BEFORE the counter
thread starts, if it's not created before the counter thread starts, then your counter thread will be calling setText
on an uninitialized object.
In order to ensure that the race condition is eliminated we must guarantee the order of execution and one way to guarantee it is to execute showGUI()
and counter.start()
sequentially on the same thread:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
showGUI();
counter.start();
}
});
}
Now showGUI();
and counter.start();
are executed from the same thread, thus the JLabel
will be created before the counter
is started.
Update:
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
这篇关于事件发送线程如何工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!