事件调度线程如何工作?
在 stackoverflow 上的人们的帮助下,我能够获取以下简单 GUI 倒计时的工作代码(仅显示倒计时秒数的窗口)。这段代码的主要问题是 invokeLater
内容。
据我了解 invokeLater
,它将一个任务发送到事件调度线程 (EDT),然后 EDT 在“可以”时执行该任务(无论这意味着什么)。 是吗?
据我了解,代码的工作原理如下:
在
main
方法中,我们使用invokeLater
来显示窗口(showGUI
方法)。换句话说,显示窗口的代码将在 EDT 中执行。在
main
方法中,我们还启动了计数器
,并且计数器(通过构造)在另一个线程中执行(因此它不在事件调度线程中)。对吗?计数器在单独的线程中执行,并定期调用
updateGUI
。updateGUI
应该更新 GUI。 GUI 正在 EDT 中运行。因此,updateGUI
也应该在 EDT 中执行。这就是updateGUI
的代码包含在invokeLater
中的原因。对吗?
我不清楚为什么我们从 EDT 调用计数器。无论如何,它不是在 EDT 中执行的。它立即启动,一个新线程和计数器在那里执行。那么,为什么我们不能在 main 方法的 invokeLater
块之后调用 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();
}
});
}
}
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();
}
});
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
如果我正确理解你的问题,你会想知道为什么你不能这样做:
你不能这样做的原因是因为调度程序不做任何保证......只是因为你调用了
showGUI()
然后调用counter.start()
并不意味着showGUI()
中的代码将在run 方法中的代码之前执行计数器。
可以这样想:
启动一个线程,该线程在 EDT 上安排一个异步事件,该事件的任务是创建JLabel
。JLabel
的存在,因此它可以调用label.setText("You have " + i + "秒。");
现在你有一个竞争条件:
JLabel
必须在counter
线程启动之前创建,如果它没有在计数器线程启动之前创建,那么你的计数器线程将在未初始化的对象上调用setText
。为了确保消除竞争条件,我们必须保证执行顺序,并且一种方法保证它是执行
showGUI()
和counter.start ()
在同一线程上依次执行:现在
showGUI();
和counter.start();
从同一线程执行,因此JLabel
将在计数器
启动之前创建。更新:
If I understand your question correctly you're wonder why you can't do this:
The reason why you can't do it is because the scheduler makes no guarantees... just because you invoked
showGUI()
and then you invokedcounter.start()
doesn't mean that the code inshowGUI()
will be executed before the code in the run method of thecounter
.Think of it this way:
starts a thread and that thread isschedules an asynchronous event on the EDT which is tasked with creating theJLabel
.JLabel
to exists so it can calllabel.setText("You have " + i + " seconds.");
Now you have a race condition:
JLabel
must be created BEFORE thecounter
thread starts, if it's not created before the counter thread starts, then your counter thread will be callingsetText
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()
andcounter.start()
sequentially on the same thread:Now
showGUI();
andcounter.start();
are executed from the same thread, thus theJLabel
will be created before thecounter
is started.Update:
您实际上是从 EDT启动
计数器
线程。如果您在invokeLater
块之后调用counter.start()
,则计数器可能会在 GUI 可见之前开始运行。现在,因为您正在 EDT 中构建 GUI,所以当计数器
开始更新 GUI 时,GUI 将存在。幸运的是,您似乎将 GUI 更新转发到 EDT,这是正确的,并且由于 EventQueue 是一个队列,第一次更新将在 GUI 构建之后发生,因此应该没有理由这不起作用。但是更新可能还不可见的 GUI 有何意义呢?You are actually starting the
counter
thread from the EDT. If you calledcounter.start()
after theinvokeLater
block, the counter would likely start to run before the GUI becomes visible. Now, because you're constructing the GUI in the EDT, the GUI wouldn't exist when thecounter
starts to update it. Luckily you seem to be forwarding the GUI updates to the EDT, which is correct, and since the EventQueue is a queue, the first update will happen after the GUI has been constructed, so there should be no reason why this wouldn't work. But what's the point of updating a GUI that may not be visible yet?这是一个解决 Swing API 存在的大量并发问题的巧妙解决方法;)
说真的,很多 Swing 组件都不是“线程安全的”(一些著名的程序员甚至称 Swing 为“线程敌对”)。通过拥有一个独特的线程,所有更新都针对该线程敌对组件进行,您可以避免许多潜在的并发问题。除此之外,您还可以保证它会按顺序运行您使用
invokeLater
传递给它的Runnable
。然后是一些挑剔:
然后:
您实际上并没有在主方法中启动计数器。您可以在 EDT 上执行的匿名
Runnable
的 run() 方法中启动计数器。因此,您实际上是从 EDT 启动计数器Thread
,而不是从 main 方法。然后,因为它是一个单独的线程,所以它不会在 EDT 上运行。但计数器肯定在 EDT 上启动,而不是在执行main(...)
方法的Thread
中启动。这是挑剔的,但我认为看到这个问题仍然很重要。
It's a hacky workaround around the great many concurrency issues that the Swing API has ;)
Seriously, a lot of Swing components are not "thread safe" (some famous programmers went as far as calling Swing "thread hostile"). By having a unique thread where all updates are made to this thread-hostile components you're dodging a lot of potential concurrency issues. In addition to that, you're also guaranteed that it shall run the
Runnable
that you pass through it usinginvokeLater
in a sequential order.Then some nitpicking:
And then:
You don't really start the counter in the main method. You start the counter in the run() method of the anonymous
Runnable
that is executed on the EDT. So you really start the counterThread
from the EDT, not the main method. Then, because it's a separate Thread, it is not run on the EDT. But the counter definitely is started on the EDT, not in theThread
executing themain(...)
method.It's nitpicking but still important seen the question I think.
这个很简单,如下
步骤1。创建初始线程也称为主线程。
步骤 2. 创建一个可运行对象并将其传递给 invokeLate()。
步骤 3. 这会初始化 GUI,但不会创建 GUI。
步骤 4. InvokeLater() 安排创建的对象在 EDT 上执行。
步骤 5. GUI 已创建。
步骤 6. 所有发生的事件都将置于 EDT 中。
This is simple, it is as follows
Step 1 . Initial thread also called main thread is created.
Step 2. Create a runnable object and pass it to invokeLate().
Step 3. This initialises the GUI but does not create the GUI.
Step 4. InvokeLater() schedules the created object for execution on EDT.
Step 5. GUI has been created.
Step 6. All events occurring will be placed in EDT.