【问题标题】:Why does SwingUtilities.invokeLater() cause JButton to freeze?为什么 SwingUtilities.invokeLater() 会导致 JButton 冻结?
【发布时间】:2015-07-22 18:56:44
【问题描述】:

考虑一下这个由两个按钮组成的基本 Swing 程序:

public class main {

    public static void main(String[] args) {
        JFrame jf = new JFrame("hi!");
        JPanel mainPanel = new JPanel(new GridLayout());
        JButton longAction = new JButton("long action");
        longAction.addActionListener(event -> doLongAction());
        JButton testSystemOut = new JButton("test System.out");
        testSystemOut.addActionListener(event -> System.out.println("this is a test"));
        mainPanel.add(longAction);
        mainPanel.add(testSystemOut);
        jf.add(mainPanel);
        jf.pack();
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jf.setVisible(true);
    }

    public static void doLongAction() {
        SwingUtilities.invokeLater(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                System.out.println("Interrupted!");
            }
            System.out.println("Finished long action");
        });

    }
}

我希望我的第二个按钮 testSystemOut 在第一个按钮进行长时间操作时可用(在这里,我在其中设置了 3 秒的睡眠时间)。我可以通过手动将doLongAction() 放入Thread 并调用start() 来做到这一点。但我读过我应该改用SwingUtilities,它的工作原理与这里的EventQueue 完全一样。但是,如果我这样做,我的 Button 会在其操作期间冻结。

为什么?

【问题讨论】:

    标签: java multithreading swing


    【解决方案1】:

    通过使用SwingUtilities.invokeLater,您在 Swing 事件线程上调用包含的代码,包括 Thread.sleep(...) 调用,这是您应该永远不要做的事情,因为它会将整个事件线程,负责绘制您的 GUI 并响应用户输入、休眠的线程——也就是说,它会冻结您的应用程序。解决方案:改用Swing Timer 或在后台线程中睡觉。如果您正在调用长时间运行的代码并使用Thread.sleep(...) 来模拟它,那么请使用 SwingWorker 为您完成后台工作。有关详细信息,请阅读Concurrency in Swing。请注意,没有理由将 SwingUtilities.invokeLater 放在哪里,因为无论如何都会在 EDT(Swing 事件线程)上调用 ActionListener 代码。但是,我会在您创建 GUI 的地方使用 SwingUtilities.invokeLater

    例如,

    import java.awt.*;
    import java.awt.event.*;
    import java.beans.PropertyChangeEvent;
    import java.beans.PropertyChangeListener;
    import java.util.concurrent.ExecutionException;
    import javax.swing.*;
    
    public class Main {
    
       public static void main(String[] args) {
          SwingUtilities.invokeLater(new Runnable() {
             @Override
             public void run() {
                JFrame jf = new JFrame("hi!");
                JPanel mainPanel = new JPanel(new GridLayout());
                JButton testSystemOut = new JButton("test System.out");
                testSystemOut.addActionListener(new ActionListener() {
    
                   @Override
                   public void actionPerformed(ActionEvent e) {
                      System.out.println("this is a test");
                   }
                });
                mainPanel.add(new JButton(new LongAction("Long Action")));
                mainPanel.add(new JButton(new TimerAction("Timer Action")));
                mainPanel.add(testSystemOut);
                jf.add(mainPanel);
                jf.pack();          
                jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                jf.setVisible(true);
             }
          });
       }
    
       @SuppressWarnings("serial")
       public static class LongAction extends AbstractAction {
          private LongWorker longWorker = null;
    
          public LongAction(String name) {
             super(name);
             int mnemonic = (int) name.charAt(0);
             putValue(MNEMONIC_KEY, mnemonic);
          }
    
          @Override
          public void actionPerformed(ActionEvent e) {
             setEnabled(false);
             longWorker = new LongWorker(); // create a new SwingWorker
    
             // add listener to respond to completion of the worker's work
             longWorker.addPropertyChangeListener(new LongWorkerListener(this));
    
             // run the worker
             longWorker.execute();
          }
       }
    
       public static class LongWorker extends SwingWorker<Void, Void> {
          private static final long SLEEP_TIME = 3 * 1000;
    
          @Override
          protected Void doInBackground() throws Exception {
             Thread.sleep(SLEEP_TIME);
    
             System.out.println("Finished with long action!");
             return null;
          }
       }
    
       public static class LongWorkerListener implements PropertyChangeListener {
          private LongAction longAction;
    
          public LongWorkerListener(LongAction longAction) {
             this.longAction = longAction;
          }
    
          @Override
          public void propertyChange(PropertyChangeEvent evt) {
             if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
                // if the worker is done, re-enable the Action and thus the JButton
                longAction.setEnabled(true);
                LongWorker worker = (LongWorker) evt.getSource();
                try {
                   // call get to trap any exceptions that might have happened during worker's run
                   worker.get();
                } catch (InterruptedException e) {
                   e.printStackTrace();
                } catch (ExecutionException e) {
                   e.printStackTrace();
                }
             }
          }
       }
    
       @SuppressWarnings("serial")
       public static class TimerAction extends AbstractAction {
          private static final int TIMER_DELAY = 3 * 1000;
    
          public TimerAction(String name) {
             super(name);
             int mnemonic = (int) name.charAt(0);
             putValue(MNEMONIC_KEY, mnemonic);
          }
    
          @Override
          public void actionPerformed(ActionEvent e) {
             setEnabled(false);
             new Timer(TIMER_DELAY, new TimerListener(this)).start();
          }
       }
    
       public static class TimerListener implements ActionListener {
          private TimerAction timerAction;
    
          public TimerListener(TimerAction timerAction) {
             this.timerAction = timerAction;
          }
    
          @Override
          public void actionPerformed(ActionEvent e) {
             timerAction.setEnabled(true);
             System.out.println("Finished Timer Action!");
             ((Timer) e.getSource()).stop();
          }
       }
    }
    

    【讨论】:

    • 谢谢,我明白了。为什么我不应该在我的longAction 按钮上简单地使用addActionListener(event -&gt; new Thread(() -&gt; doLongAction()).start());(自定义线程)而不是定义我自己的SwingWorker,有什么原因吗? (谈论 LongWorker,而不是 TimerAction)
    • @Blauhirn:这也可以,但使用 SwingWorker 有几个优点,包括它的发布/处理方法对,可以轻松地将数据从后台线程传递到 GUI事件线程,例如 SwingWorker 的进度属性,可以很容易地在 JProgressBar 中显示 SwingWorker 的工作进度,例如它很容易与 PropertyChangeListener 一起使用,当 SwingWorker 完成任务时很容易得到通知。 SwingWorker 也可以在完成后根据需要返回一个值。
    【解决方案2】:

    当你想执行一些长时间运行的代码时,不要使用SwingUtilities.invokeLater(...)。在单独的普通线程中执行此操作。

    Swing 不是多线程的,它是事件驱动的。因此,有类似SwingUtilities.invokeLater(...) 的方法。如果您想从不同的线程更改 Swing 组件(因为 Swing 不是线程安全的),您必须使用这些方法,例如,如果您想更改按钮的文本。 与 GUI 相关的所有内容都在该 Swing-Thread 中运行,例如光标闪烁、来自操作系统的消息、用户命令等。 由于它是一个单线程,因此该线程中的每个长时间运行的代码都会阻塞您的 GUI。

    如果你只是做一些与 GUI 无关的长时间运行的代码,它不应该在 Swing-Event-Thread 中运行,而是在它自己的独立线程中运行。

    https://weblogs.java.net/blog/kgh/archive/2004/10/multithreaded_t.html 为什么 Swing 不是多线程的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-12-11
      • 2017-08-18
      • 2017-06-17
      • 1970-01-01
      • 1970-01-01
      • 2015-06-21
      • 2014-07-01
      • 2012-10-06
      相关资源
      最近更新 更多