使用invokeLater或SwingWorker从另一个线程更新Swing组件

8
我正在开发一个小应用程序,它将具有Swing GUI。应用程序在另一个线程中执行IO任务,当该线程完成时,GUI应相应更新以反映线程的操作结果。在运行于(工作器、非GUI)中的类中,构造函数中传递了一个对象,该对象将用于更新GUI,因此我不需要在非GUI类中放置GUI内容,而是将对象传递给该类以更新GUI。
根据我从这里阅读的理解,更新(更改)Swing GUI 的线程/ swing 安全选项将使用javax.swing.SwingUtilities.invokeLater()javax.swing.SwingUtilities.invokeLaterWait() 和/或javax.swing.SwingWorker(),这些选项基本上都是在做同样的事情。
对于我来说,Swing 中的所有这些线程问题有点令人困惑,但我需要使用线程在 GUI 应用程序中执行任何有意义的操作,并且在 EDT 中处理时不会挂起 GUI,所以现在我感兴趣的是:
  1. Are invokeLater and invokeLaterWait like sending message to EDT and waiting for it do it when it finishes processing messages that were before that call?

  2. is it correct from Swing thread safety aspect, to do something like this:

    interface IUPDATEGUI {  
      public void update();  
    }  
    
    // in EDT/where I can access components directly    
    class UpdateJList implements IUPDATEGUI {    
      public void update() {    
        // update JList...    
        someJList.revalidate();    
        someJList.repain();    
      }    
    }    
    
    class FileOperations implements Runnable {  
      private IUPDATEGUI upObj;  
      List<File> result = new ArrayList<File>; // upObject is accessing this  
    
      public void FileOperations(IUPDATEGUI upObj) {
        this.upObj = upObj;
      }
    
      private void someIOTask() {
        // ...
        // IO processing finished, result is in "result"
      }
    
      public void run() {
        someIOTask();
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
          public void run() {
            upObj.update();  // access result and update JList
          }
        }; );
      }
    }
    
如果这不正确,应该如何操作?
如果可能的话,我更愿意使用invokeLater而不是SwingWorker,因为我不需要改变整个类,而且它更加简洁明了(就像在Win32应用程序中发送消息一样)。
提前感谢。
3个回答

5
使用invokeLater()invokeAndWait()将Runnable参数传递到等待在EDT中执行的队列中。因此,调用invokeLater()会在EDT能够处理请求时使Runnable在EDT中执行。invokeAndWait()只是等待(在调用线程中),直到此执行发生。
如果要执行通知EDT的后台任务,则使用SwingWorker是理想的选择,无论是在执行结束时还是在中间状态下。例如,将进程的当前进度传递给JProgressBar。
对于您的示例,似乎SwingWorker是更好的选择,但如果您不想太多更改代码,那么在完成进程时调用invokeLater()就可以了。

1
请问你能否告诉我更多,为什么你认为SwingerWorker在我的情况下是更好的选择? - Mil
我想我明白了。如果我错了,请纠正我,基本上我可以在SwingWorker类的两种方法中更新GUI组件,例如我可以在doInBackground()中更新JProgressBar,在完成时,我可以在SwingWorker类的done()方法中更新JList? - Mil
2
当您希望在进程仍在运行时向GUI发布中间更新时,可以使用SwingWorker的publish()方法,该方法在EDT中调用process()方法。而当进程完成时,done()方法也会在EDT中被调用。因此,您有两个选项来更新GUI:一个是在进程运行时(publish),另一个是在完成时(done)。阅读这篇文章:http://java.sun.com/javase/6/docs/api/javax/swing/SwingWorker.html,它非常有帮助。 - Savvas Dalkitsis

3

我建议在Java 7之前不要使用invokeAndWait。我发现这个方法存在一些虚假唤醒的问题,可能会导致非常痛苦的bug。对我来说,它导致了一些罕见且难以调试的空指针异常。

http://bugs.sun.com/view_bug.do?bug_id=6852111

这个问题在Java 7 b77中已经修复。


+1 因为发布关于虚假唤醒的内容...“循环等待”是条件的标准习语,所以我想知道为什么他们一开始没有这样做。 - C. K. Young
我几年前发布了修复该问题的代码。实际上不应该有问题,因为Sun JVM从不会出现虚假唤醒。如果你看到空指针异常,可能是由其他原因引起的。无论如何,invokeAndWait往往会导致死锁,所以应该避免使用。 - Tom Hawtin - tackline
请查看这对旧的CR:http://bugs.sun.com/view_bug.do?bug_id=4974934 http://bugs.sun.com/view_bug.do?bug_id=4932181 - Tom Hawtin - tackline
有趣。你对Sun JVM从不出现虚假唤醒这一点百分之百确定吗?这与我们在生产环境中看到的很多情况相矛盾。 - reccles

0

invokeLater 是一个不错的选择。它会将调用放入 AWT 事件队列中,以便在适当时候在 EDT 中执行。您的程序将继续运行,而不必等待可调用对象被调用。


在这种情况下,在更新组件后,需要调用revalidate()和repaint()吗? - Mil

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接