Java - SwingWorker - 我们可以在另一个SwingWorker中调用一个SwingWorker而不是EDT吗?

3

我有一个SwingWorker如下:

public class MainWorker extends SwingWorker(Void, MyObject) {
    :
    :
}

我从EDT调用了上述的Swing Worker

MainWorker mainWorker = new MainWorker();
mainWorker.execute();

现在,mainWorker创建了10个MyTask类的实例,以便每个实例都可以在自己的线程上运行,以加快完成工作的速度。

但问题是,在任务运行期间,我想不时更新GUI。我知道,如果任务由mainWorker本身执行,我可以使用publish()process()方法来更新GUI。

但由于这些任务是由与Swingworker线程不同的线程执行的,因此我该如何从执行任务的线程生成的中间结果更新GUI呢?


你想要发布到GUI的是什么样的结果?它们是子任务特定的结果(例如,每个子任务正在做什么),还是来自所有任务的聚合结果(例如,所有任务的完成百分比)? - mdma
我想将一个Object发布到GUI,以用于填充JTable - Amit
4个回答

8
SwingWorker的API文档提供了以下提示:
doInBackground()方法在此线程上调用。这是所有后台活动应该发生的地方。要通知PropertyChangeListeners有关绑定属性更改,请使用firePropertyChange和getPropertyChangeSupport()方法。默认情况下,有两个可用的绑定属性:state和progress。
MainWorker可以实现PropertyChangeListener。然后,它可以使用其PropertyChangeSupport向自身注册:
getPropertyChangeSupport().addPropertyChangeListener( this );

MainWorker 可以将其 PropertyChangeSupport 对象提供给它创建的每个 MyTask 对象。

new MyTask( ..., this.getPropertyChangeSupport() );

一个MyTask对象可以通过使用PropertyChangeSupport.firePropertyChange方法通知其MainWorker进度或属性更新。

MainWorker收到通知后,可以使用SwingUtilities.invokeLaterSwingUtilities.invokeAndWait通过EDT更新Swing组件。

protected Void doInBackground() {
    final int TASK_COUNT = 10;
    getPropertyChangeSupport().addPropertyChangeListener(this);
    CountDownLatch latch = new CountDownLatch( TASK_COUNT ); // java.util.concurrent
    Collection<Thread> threads = new HashSet<Thread>();
    for (int i = 0; i < TASK_COUNT; i++) {
        MyTask task = new MyTask( ..., latch, this.getPropertyChangeSupport() ) );
        threads.add( new Thread( task ) );
    }
    for (Thread thread: threads) {
        thread.start();
    }
    latch.await();
    return null;
}

+1 对于 CountDownLatch,我之前在你的回答中忽略了它。 - trashgod
使用 latch.await() 确保子线程所做的更改将在 doInBackground() 结束时可见。 - Justin
假设这些任务是独立的,如何通过firePropertyChange通知更新来自哪个任务?例如,每个任务都会更新其完成百分比,后台线程如何计算所有任务的总完成百分比? - mdma
@mdma 可以通过 PropertyChangeEvent 发射器提供执行该计算所需的信息;例如,作为表示元组(任务名称、完成百分比、总工作百分比)的 bean。firePropertyChange 接受 java.lang.Object 作为属性值。 - Noel Ang
嗨,Noel,我正在考虑实现类似的解决方案。我想知道,8年后,上面的代码仍然是最好的解决方案吗?还是你会对它进行任何更改? - Colm Bhandal

3
即使您不使用 SwingWorker,也可以使用 SwingUtilities.invokeLater(...)或 SwingUtilities.invokeAndWait(...)将要在EDT中执行的任务提交。 编辑: 假设您有一个正在执行某些代码的线程,您始终可以像下面的示例一样与EDT交互。
public void aMethodExecutedInAThread() {

    // Do some computation, calculation in a separated Thread

    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            // Post information in the EDT
            // This code is executed inside EDT
        }
    });
}

@Matthieu - 你能举个例子或提供一些链接来详细说明吗? - Amit
@Yatendra Goel:请查看“Continuations as objects”。http://en.wikipedia.org/wiki/Continuation_passing_style#Continuations_as_objects - trashgod
在这里要小心内存一致性问题,由aMethodExecutedInAThread()方法所做的更改可能对于EDT来说是不可见的,除非EDT在包含更改的对象上进行同步(或者修改的属性被标记为volatile)。 - Justin
@Justin:您是什么意思?SwingUtilities 工作得很好,而且已经使用了一段时间。 - Matthieu BROUILLARD
aMethodExecutedInAThrad() 所执行的操作(内存写入)需要与在 EDT 中运行的匿名可运行对象建立 happens-before 关系。从 EventQueue(或 SwingUtilities)实际上并不清楚 invokeLater() 和 EDT 是否获取相同的锁(尽管它们可能在内部这样做)。 - Justin
嗯... invokeLater() 的目标是在EDT中执行而不与其他EDT相关的东西发生冲突,是吗?我猜我可能从你的评论中漏掉了一些东西。 - Matthieu BROUILLARD

2

为什么要使用EventQueue.invokeLater()而不是SwingUtilities.invokeLater() - Amit
从1.3版本开始,这个方法只是java.awt.EventQueue.invokeLater()的一个封装。" http://java.sun.com/javase/6/docs/api/javax/swing/SwingUtilities.html#invokeLater%28java.lang.Runnable%29 - trashgod

0

以上所有文章中提到的SwingWorker已经过时。自Java 1.6以来,它已成为JDK的一部分,并且与先前的SwingWorker不同。 - Amit
@Yatendra Goel:Java 5有一个源兼容的后移版本。https://swingworker.dev.java.net/ - trashgod

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