问题描述
我对Swing 事件调度程序线程(EDT)的理解是,它是执行事件处理代码的专用线程.因此,如果我的理解是正确的,那么在下面的示例中:
My understanding of the Swing Event Dispatcher Thread (EDT) is that its a dedicated thread where event handling code is executed. So, if my understanding is correct, then in the example below:
private class ButtonClickListener implements ActionListener{ public void actionPerformed(ActionEvent e) { // START EDT String command = e.getActionCommand(); if( command.equals( "OK" )) { statusLabel.setText("Ok Button clicked."); } else if( command.equals( "Submit" ) ) { statusLabel.setText("Submit Button clicked."); } else { statusLabel.setText("Cancel Button clicked."); } // END EDT } }
START EDT和END EDT之间的所有代码都在EDT上执行,而它之外的任何代码都在主应用程序线程上执行.同样,另一个示例:
All the code in between START EDT and END EDT is executing on the EDT, and any code outside of it is executing on the main application thread. Similarly, another example:
// OUTSIDE EDT JFrame mainFrame = new JFrame("Java SWING Examples"); mainFrame.setSize(400,400); mainFrame.setLayout(new GridLayout(3, 1)); mainFrame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent windowEvent){ // START EDT System.exit(0); // END EDT } // BACK TO BEING OUTSIDE THE EDT });
同样,在EDT中仅执行System.exit(0).
Again, only the System.exit(0) is executed inside the EDT.
因此,对于初学者来说,如果我对EDT与主应用程序线程代码执行之间的分工"理解不正确,请先纠正我!
现在,我遇到了一篇文章,强调使用从所有这些EDT代码内部创建新的Thread的用法,这将使上面的第一个示例看起来像这样:
Now then, I came across an article that emphasized the use of creating a new Thread from inside all this EDT code, which would make my first example above look like this:
public class LabelUpdater implements Runnable { private JLabel statusLabel; private ActionEvent actionEvent; // ctor omitted here for brevity @Override public void run() { String command = actionEvent.getActionCommand(); if (command.equals( "OK" )) { statusLabel.setText("Ok Button clicked."); } else if( command.equals( "Submit" ) ) { statusLabel.setText("Submit Button clicked."); } else { statusLabel.setText("Cancel Button clicked."); } } } private class ButtonClickListener implements ActionListener{ public void actionPerformed(ActionEvent e) { // START EDT Thread thread = new Thread(new LabelUpdater(statusLabel, e)); thread.start(); // END EDT } }
我的问题:这种方法有什么优势(或没有优势)?我应该以这种方式总是编码我的EDT代码,还是有一个需要遵循的规则作为何时应用的准则?预先感谢!
My question: what advantage (or lack thereof) is there to this approach? Should I always code my EDT code this way, or is there a rubric one needs to follow as a guidelines for when to apply it? Thanks in advance!
推荐答案
这个问题有点笼统和不确定,但是我将尝试解决您所提出的一些问题.进一步进行自己研究的切入点可能是课程:Swing中的并发,尽管确实很难从中得出针对特定情况的明确陈述.
The question is a bit broad and unspecific, but I'll try to address some of the points that you asked about. The entry point for further, own research is probably the Lesson: Concurrency in Swing, although it may indeed be hard to derive definite statements for specific cases from that.
首先,Swing中有一个总体规则-称为单线程规则:
First of all, there is an overarching rule in Swing - referred to as the Single Thread Rule:
记住这一点,看看您的代码片段:
Keeping that in mind, looking at your snippets:
// OUTSIDE EDT JFrame mainFrame = new JFrame("Java SWING Examples"); ...
不幸的是,这是正确的,而且不幸的是,即使在某些Swing官方示例中,也是如此.但这可能已经引起问题.为了安全起见,应始终使用 SwingUtilities#invokeLater .模式始终是相同的:
This is often true, unfortunately - and unfortunately, even in some of the official Swing examples. But this may already cause problems. To be on the safe side, the GUI (including the main frame) should always be handled on the EDT, using SwingUtilities#invokeLater. The pattern is always the same then:
public static void main(String[] args) { SwingUtilities.invokeLater(() -> createAndShowGui()); } private static void createAndShowGui() { JFrame mainFrame = new JFrame("Java SWING Examples"); ... mainFrame.setVisible(true); }
关于您显示的涉及LabelUpdater类的第二个示例:我很好奇您从哪篇文章中得到的.我知道,那里有很多cr4p,但是这个示例甚至没有意义...
Regarding the second example that you showed, involving the LabelUpdater class: I'd be curious from which article you got this. I know, there is a lot of cr4p out there, but this example doesn't even remotely make sense...
public class LabelUpdater implements Runnable { private JLabel statusLabel; ... @Override public void run() { ... statusLabel.setText("Ok Button clicked."); } }
如果该代码(即run方法)在新线程中执行,则显然违反了单线程规则:JLabel是从不是事件分发线程的 线程修改的!
If this code (i.e. the run method) is executed in an new thread, then it obviously violates the single thread rule: The status of the the JLabel is modified from a thread that is not the event dispatch thread!
在事件处理程序中(例如,在ActionListener的actionPerformed方法中)启动新线程的主要目的是防止阻塞用户界面.如果您有这样的代码
The main point of starting a new thread in an event handler (e.g. in an actionPerformed method of an ActionListener) is to prevent blocking the user interface. If you had some code like this
someButton.addActionListener(e -> { doSomeComputationThatTakesFiveMinutes(); someLabel.setText("Finished"); });
然后按下按钮将使EDT阻塞5分钟-即GUI将冻结",并且看起来像挂了.在这种情况下(例如,您需要长时间运行计算),您应该在自己的线程中进行这项工作.
then pressing the button would cause the EDT to be blocked for 5 minutes - i.e. the GUI would "freeze", and look like it hung up. In these cases (i.e. when you have long-running computations), you should do the work in an own thread.
手动执行此操作的幼稚方法 (大致)如下:
The naive approach of doing this manually could (roughly) look like this:
someButton.addActionListener(e -> { startBackgroundThread(); }); private void startBackgroundThread() { Thread thread = new Thread(() -> { doSomeComputationThatTakesFiveMinutes(); someLabel.setText("Finished"); // WARNING - see notes below! }); thread.start(); }
现在,按下按钮将启动一个新线程,并且GUI将不再阻塞.但是请注意代码中的WARNING:现在又出现了一个问题,该问题是不是事件分派线程的线程修改了JLabel!因此,您必须将其传递回EDT:
Now, pressing the button would start a new thread, and the GUI would no longer block. But note the WARNING in the code: Now there's this problem again of the JLabel being modified by a thread that is not the event dispatch thread! So you'd have to pass this back to the EDT:
private void startBackgroundThread() { Thread thread = new Thread(() -> { doSomeComputationThatTakesFiveMinutes(); // Do this on the EDT again... SwingUtilities.invokeLater(() -> { someLabel.setText("Finished"); }); }); thread.start(); }
这看起来笨拙而复杂,似乎您可能很难确定当前使用的线程.没错.但是对于开始长期运行任务的常见任务,有教程中解释的SwingWorker类使该模式更简单.
This may look clumsy and complicated, and as if you could have a hard time figuring out on which thread you currently are. And that's right. But for the common task of starting a long-running task, there is the SwingWorker class explained in the tutorial that makes this pattern somewhat simpler.
无耻的自我推广:不久前,我创建了一个 SwingTasks库,基本上是类固醇的摇摆工人".它允许您连接"这样的方法...
Shameless self-promotion: A while ago, I created a SwingTasks library, which is basically a "Swing Worker on steroids". It allows you to "wire up" methods like this...
SwingTaskExecutors.create( () -> computeTheResult(), result -> receiveTheResult(result) ).build().execute();
,如果执行时间过长,则会显示(模式)对话框,并提供其他一些便捷方法,例如用于在对话框中显示进度条等.样本在 https ://github.com/javagl/SwingTasks/tree/master/src/test/java/de/javagl/swing/tasks/samples
and takes care of showing a (modal) dialog if the execution takes too long, and offers some other convenience methods, e.g. for showing a progress bar in the dialog and so on. The samples are summarized at https://github.com/javagl/SwingTasks/tree/master/src/test/java/de/javagl/swing/tasks/samples
这篇关于从Swing应用程序的EDT事件处理程序代码内部启动线程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!