简体   繁体   English

从 Swing 应用程序中的 EDT 事件处理程序代码内部启动线程

[英]Starting threads from inside EDT event handler code in Swing apps

My understanding of the Swing Event Dispatcher Thread (EDT) is that its a dedicated thread where event handling code is executed.我对 Swing事件调度线程 (EDT)的理解是,它是执行事件处理代码的专用线程。 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
   }        
}

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. START EDTEND EDT之间的所有代码都在 EDT 上执行,并且它之外的任何代码都在主应用程序线程上执行。 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
});  

Again, only the System.exit(0) is executed inside the EDT.同样,只有System.exit(0)在 EDT 内执行。

So for starters, if my understanding of the "division of labor" between EDT and main app thread code execution is incorrect, please begin by correcting me!所以对于初学者来说,如果我对 EDT 和主应用程序线程代码执行之间的“分工”的理解不正确,请先纠正我!

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:现在,我遇到了一篇文章,它强调了从所有这些 EDT 代码中创建一个新Thread的使用,这将使我上面的第一个示例看起来像这样:

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
   }        
}

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?我是否应该始终以这种方式编写我的 EDT 代码,或者是否需要遵循一个准则作为何时应用它的指导方针? Thanks in advance!提前致谢!

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 :首先,在 Swing 中有一个总体规则——称为单线程规则

Once a Swing component has been realized, all code that might affect or depend on the state of that component should be executed in the event-dispatching thread.一旦实现了 Swing 组件,所有可能影响或依赖于该组件的 state 的代码都应在事件调度线程中执行。

(Unfortunately, it is no longer stated so clearly in the tutorial) (很遗憾,教程中已经没有说的那么清楚了)


Keeping that in mind, looking at your snippets:记住这一点,看看你的片段:

// OUTSIDE EDT
JFrame mainFrame = new JFrame("Java SWING Examples");
...

This is often true, unfortunately - and unfortunately, even in some of the official Swing examples.不幸的是,这通常是正确的 - 不幸的是,即使在一些官方的 Swing 示例中也是如此。 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 .为了安全起见,应始终在 EDT 上使用SwingUtilities#invokeLater处理 GUI(包括主框架)。 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);
}

Regarding the second example that you showed, involving the LabelUpdater class: I'd be curious from which article you got this.关于您展示的第二个示例,涉及LabelUpdater class:我很好奇您是从哪篇文章中得到的。 I know, there is a lot of cr4p out there, but this example doesn't even remotely make sense...我知道,那里有很多 cr4p,但这个例子甚至没有任何意义......

public class LabelUpdater implements Runnable {
    private JLabel statusLabel;
    ...

    @Override
    public void run() {
        ...
        statusLabel.setText("Ok Button clicked.");
    }
}

If this code (ie 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!如果这段代码(即run方法)在一个新线程中执行,那么它显然违反了单线程规则JLabel的状态是从一个不是事件调度线程的线程修改的!


The main point of starting a new thread in an event handler (eg in an actionPerformed method of an ActionListener ) is to prevent blocking the user interface .在事件处理程序中(例如,在ActionListeneractionPerformed方法中)启动新线程的主要目的是防止阻塞用户界面 If you had some code like this如果你有这样的代码

someButton.addActionListener(e -> {
    doSomeComputationThatTakesFiveMinutes();
    someLabel.setText("Finished");
});

then pressing the button would cause the EDT to be blocked for 5 minutes - ie the GUI would "freeze", and look like it hung up.然后按下按钮将导致 EDT 被阻塞 5 分钟 - 即 GUI 将“冻结”,看起来像挂了。 In these cases (ie 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();
}

Now, pressing the button would start a new thread, and the GUI would no longer block.现在,按下按钮将启动一个新线程,并且 GUI 将不再阻塞。 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:但请注意代码中的WARNING :现在又出现了JLabel被一个不是事件调度线程的线程修改的问题:所以你必须将它传递回 EDT:

private void startBackgroundThread() {
    Thread thread = new Thread(() -> {
        doSomeComputationThatTakesFiveMinutes();

        // Do this on the EDT again...
        SwingUtilities.invokeLater(() -> {
            someLabel.setText("Finished");
        });
    });
    thread.start();
}

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.但是对于启动长时间运行任务的常见任务,有教程中解释的SwingWorker class使这种模式更简单一些。


Shameless self-promotion: A while ago, I created a SwingTasks library , which is basically a "Swing Worker on steroids".无耻的自我推销:前段时间,我创建了一个SwingTasks,它基本上是一个“类固醇上的 Swing Worker”。 It allows you to "wire up" methods like this...它允许你“连接”这样的方法......

SwingTaskExecutors.create(
    () -> computeTheResult(),
    result -> receiveTheResult(result)
).build().execute();

and takes care of showing a (modal) dialog if the execution takes too long, and offers some other convenience methods, eg 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样本汇总在https://github.com/javagl/SwingTasks/tree/master/src/test/java/de/javagl/swing/tasks/samples

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM