SwingWorker:done方法何时被调用?

21

SwingWorkerdone()方法的Javadoc:

doInBackground方法完成后,在事件派发线程上执行。

我有线索表明在取消工作线程的情况下,这不是正确的。done无论是在正常终止还是取消时都会被调用,但是当cancelled为真时,它不会被加入到事件派发线程中,而这在正常终止时却会发生。

是否对于SwingWorker被取消时done方法被调用的情况有更精确的分析?

澄清:本问题不涉及如何cancel一个SwingWorker。假设SwingWorker已正确取消。
并且不涉及线程在它们应该结束时仍在运行的问题。


你为什么不看一下源代码呢? - Laurent Pireyn
啊,它在src.zip里面,但是没有.class文件。太好了... - Aaron Digulla
@AgostinoX 请查看以下两个线程 https://dev59.com/LG025IYBdhLWcg3wLipX#6186188 http://stackoverflow.com/questions/6051755/java-wait-cursor-display-problem/6060678#comment-7170467 然后请提出问题,无论如何 SwingWorker 的教程都包含了示例 http://download.oracle.com/javase/tutorial/uiswing/concurrency/index.html - mKorbel
3
@mKorblel:我非常仔细地阅读了SwingWorker的教程,并且也阅读了Java文档。如果你也这样做了,你就会知道教程只是展示了一个基本的取消示例,并没有深入讲解。 - AgostinoX
2
@mKorblel:抱歉,mKorblel,你仔细阅读了我的问题吗? - AgostinoX
显示剩余3条评论
6个回答

22

当一个线程被通过

取消
myWorkerThread.cancel(true/false);

done()方法(令人惊讶的是)会被cancel()方法本身调用。

你可能期望发生的事情,但实际上没有发生:
- 你调用cancel(无论是否带有mayInterrupt参数)
- cancel设置了线程取消
- doInBackground退出
- done()被调用*
(* done()被排队进入EDT,也就是说,如果EDT忙碌,它会在EDT完成正在进行的操作之后才会发生)

实际发生的事情:
- 你调用cancel(无论是否带有mayInterrupt参数)
- cancel设置了线程取消
- done()作为cancel代码的一部分被调用*
- doInBackground将在完成其循环后退出
(* done()不会排队进入EDT,而是直接被调用到cancel()中,因此它对EDT有非常立即的影响,通常是GUI)

我提供了一个简单的示例来证明这一点。
复制,粘贴并运行。
1. 我在done()内生成了一个运行时异常。堆栈线程显示done()是由cancel()调用的。
2. 在取消后大约4秒钟左右,您将从doInBackground获得一个问候语,进一步证明了done()在线程退出之前被调用。

import java.awt.EventQueue;
import javax.swing.SwingWorker;

public class SwingWorker05 {
public static void main(String [] args) {
    EventQueue.invokeLater(new Runnable() {
        public void run() {
            try {
            W w = new W();
            w.execute();
            Thread.sleep(1000);
            try{w.cancel(false);}catch (RuntimeException rte) {
                rte.printStackTrace();
            }
            Thread.sleep(6000);
            } catch (InterruptedException ignored_in_testing) {}
        }

    });
}

public static class W extends SwingWorker <Void, Void> {

    @Override
    protected Void doInBackground() throws Exception {
        while (!isCancelled()) {
            Thread.sleep(5000);
        }
        System.out.println("I'm still alive");
        return null;
    }

    @Override
    protected void done() {throw new RuntimeException("I want to produce a stack trace!");}

}

}

2
+1 我在大约同一时间得到了相同(令人惊讶)的结果;-) - Howard
3
因此,甚至可能出现在“完成”之后发布/处理结果的情况。 - Howard
2
这已经被Oracle确认为一个bug:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6826514 - Duncan Jones
2
那个错误报告没有更新,但在Java 7中调用#cancel()不会调用#done()(Oracle JDK)。 - searchengine27
2
抱歉,@searchengine27,但情况并非如此。我现在正在查看Java 8 JRE源代码; SwingWorker#cancel()调用FutureTask#cancel(),如果它没有立即返回false,则调用一个私有辅助方法finishCompletion(),该方法无条件调用FutureTask#done()。而SwingWorker的FutureTask匿名子类重写了done()来调用其自己的私有SwingWorker#doneEDT(),该方法无条件调用或将其自己的done()排队到EDT上。 - Ti Strga
显示剩余5条评论

6

done()方法在任何情况下都会被调用,无论工作线程是否被取消或正常完成。然而,在某些情况下,doInBackground仍在运行,而done方法已经被调用(这是在cancel()内部完成的,无论线程是否已经完成)。这里可以找到一个简单的例子:

public static void main(String[] args) throws AWTException {
    SwingWorker<Void, Void> sw = new SwingWorker<Void, Void>() {

        protected Void doInBackground() throws Exception {
            System.out.println("start");
            Thread.sleep(2000);
            System.out.println("end");
            return null;
        }

        protected void done() {
            System.out.println("done " + isCancelled());
        }
    };
    sw.execute();
    try {
        Thread.sleep(1000);
        sw.cancel(false);
        Thread.sleep(10000);
    } catch (InterruptedException ex) {
        ex.printStackTrace();
    }

因此,在doInBackground完成之前,可能会调用done

好的,代码几乎和我提供的一样。而且,我证明了“完成”是在取消中使用堆栈跟踪技巧发生的。 - AgostinoX

1

有些事情是可能的,其他可能只是幻觉

非常好的输出

run:
***removed***
java.lang.RuntimeException: I want to produce a stack trace!
        at help.SwingWorker05$W.done(SwingWorker05.java:71)
        at javax.swing.SwingWorker$5.run(SwingWorker.java:717)
        at javax.swing.SwingWorker.doneEDT(SwingWorker.java:721)
        at javax.swing.SwingWorker.access$100(SwingWorker.java:207)
        at javax.swing.SwingWorker$2.done(SwingWorker.java:284)
        at java.util.concurrent.FutureTask$Sync.innerCancel(FutureTask.java:293)
        at java.util.concurrent.FutureTask.cancel(FutureTask.java:76)
        at javax.swing.SwingWorker.cancel(SwingWorker.java:526)
        at help.SwingWorker05$1.run(SwingWorker05.java:25)
        at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:209)
        at java.awt.EventQueue.dispatchEvent(EventQueue.java:597)
        at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269)
        at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
        at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174)
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
        at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)
I'm still alive
Thread Status with Name :SwingWorker1, SwingWorker Status is STARTED
SwingWorker by tutorial's background process has completed
Thread Status with Name :SwingWorker1, SwingWorker Status is DONE
Thread Status with Name :look here what's possible with SwingWorker, SwingWorker Status is STARTED
BUILD SUCCESSFUL (total time: 10 seconds)

import java.awt.EventQueue;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.SwingWorker;

public class SwingWorker05 {

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            public void run() {
                try {
                    W w = new W();
                    w.addPropertyChangeListener(
                            new SwingWorkerCompletionWaiter("look here what's possible with SwingWorker"));
                    w.execute();
                    Thread.sleep(1000);
                    try {
                        w.cancel(false);
                    } catch (RuntimeException rte) {
                        rte.printStackTrace();
                    }
                    Thread.sleep(6000);
                } catch (InterruptedException ignored_in_testing) {
                }
            }
        });

        final MySwingWorker mySW = new MySwingWorker();
        mySW.addPropertyChangeListener(new SwingWorkerCompletionWaiter("SwingWorker1"));
        mySW.execute();
    }

    private static class MySwingWorker extends SwingWorker<Void, Void> {

        private static final long SLEEP_TIME = 250;

        @Override
        protected Void doInBackground() throws Exception {
            Thread.sleep(SLEEP_TIME);
            return null;
        }

        @Override
        protected void done() {
            System.out.println("SwingWorker by tutorial's background process has completed");
        }
    }

    public static class W extends SwingWorker {

        @Override
        protected Object doInBackground() throws Exception {
            while (!isCancelled()) {
                Thread.sleep(5000);
            }

            System.out.println("I'm still alive");
            return null;
        }

        @Override
        protected void done() {
            System.out.println("***remove***");
            throw new RuntimeException("I want to produce a stack trace!");
        }
    }

    private static class SwingWorkerCompletionWaiter implements PropertyChangeListener {

        private String str;

        SwingWorkerCompletionWaiter(String str) {
            this.str = str;
        }

        @Override
        public void propertyChange(PropertyChangeEvent event) {
            if ("state".equals(event.getPropertyName()) && SwingWorker.StateValue.DONE == event.getNewValue()) {
                System.out.println("Thread Status with Name :" + str + ", SwingWorker Status is " + event.getNewValue());
            } else if ("state".equals(event.getPropertyName()) && SwingWorker.StateValue.PENDING == event.getNewValue()) {
                System.out.println("Thread Status with Mame :" + str + ", SwingWorker Status is " + event.getNewValue());
            } else if ("state".equals(event.getPropertyName()) && SwingWorker.StateValue.STARTED == event.getNewValue()) {
                System.out.println("Thread Status with Name :" + str + ", SwingWorker Status is " + event.getNewValue());
            } else {
                System.out.println("Thread Status with Name :" + str + ", Something wrong happends ");
            }
        }
    }
}

@mKorbel。我正在研究您的示例。第一个问题。由于我的SwingWorker方法实现有6行,您能指出“错误的实现”在哪里吗?第二个问题。我从未使用过“bug”这个词。如果您阅读问题,您会发现它是关于何时调用取消的。我已经明确表明,在每种情况下(在EDT中如预期的那样!)都会调用done,并且在取消的情况下,它不会被排队到EDT,而是作为cancel(...)调用的一部分被调用,可能在退出doInBackground方法之前。现在我正在验证PropertyChangeListener的确切行为,我会让您知道。 - AgostinoX
@AgostinoX :-) 既然我实现的SwingWorker方法只有6行代码 :-) ,不是那种普通的内部类或voids 1st。等待计时器,2nd. 等待1st结束,没有其他东西,小错误... 让我们回到起点(Coldplay),最重要的是,如果你想玩SwingWorker,那么你的代码可以直接将输出确认到GUI(JTextArea),否则对我来说没有意义,因为System.out.print()在所有情况下都有效(+ - CitiBus)。祝好运 - mKorbel
好的,W类是正确的。您介意删除关于“类的错误实现”的评论吗?谢谢。然后您说问题在等待计时器?哪个计时器?也许您指的是Thread.sleep。我已经创建了一个每个人都可以使用以重现我所说的特定问题的类,因此我需要一些“挂起”几秒钟的东西,例如缓慢的数据库连接。我相信有更好的方法来解决这个问题,所以很高兴听到您的建议。 - AgostinoX
@@AgostinoX,没问题,我可以根据你的要求删除那个东西,但是正确的语法必须包括extends SwingWorker<Void, Void>/<Void, String>/<Void, Double>...<Void, Icon>,否则它就不是我们所知道的SwingWorker,只是一些需要一些方法但没有声明SwingWorkers功能的类......,所以最后我退出了这个线程。 - mKorbel
@mKorbel.I,根据您的要求,我在我的SwingWorker实现中添加了<Void,Void>,因为这将有助于习惯于查看泛型的人更好地理解该类。但是我想指出的是,功能不是由泛型提供的,而是通过扩展类或实现接口来实现的。泛型是避免类型转换的一种方式。此外,由于它们是通过擦除实现的,编译后的代码无论是否使用都是相同的;我们谈论的是方便和习惯,而不是正确性。 - AgostinoX

1

在 SwingWorker 被修复之前http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6826514 这里提供一个简单(经过测试的)版本,基本功能与 SwingWorker 类似

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package tools;

import java.util.LinkedList;
import java.util.List;
import javax.swing.SwingUtilities;

/**
 *
 * @author patrick
 */
public abstract class MySwingWorker<R,P> {

    protected abstract R doInBackground() throws Exception;
    protected abstract void done(R rvalue, Exception ex, boolean canceled);
    protected void process(List<P> chunks){}
    protected void progress(int progress){}

    private boolean cancelled=false;
    private boolean done=false;
    private boolean started=false;
    final private Object syncprogress=new Object();
    boolean progressstate=false;
    private int progress=0;
    final private Object syncprocess=new Object();
    boolean processstate=false;
    private LinkedList<P> chunkes= new LinkedList<>();

    private Thread t= new Thread(new Runnable() {
        @Override
        public void run() {
            Exception exception=null;
            R rvalue=null;
            try {
                rvalue=doInBackground();
            } catch (Exception ex) {
                exception=ex;
            }

            //Done:
            synchronized(MySwingWorker.this)
            {
                done=true;
                final Exception cexception=exception;
                final R crvalue=rvalue;
                final boolean ccancelled=cancelled;

                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        done(crvalue, cexception, ccancelled);
                    }
                });
            }

        }
    });    

    protected final void publish(P p)
    {
        if(!Thread.currentThread().equals(t))
            throw new UnsupportedOperationException("Must be called from worker Thread!");
        synchronized(syncprocess)
        {
            chunkes.add(p);
            if(!processstate)
            {
                processstate=true;
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        List<P> list;
                        synchronized(syncprocess)
                        {
                            MySwingWorker.this.processstate=false;
                            list=MySwingWorker.this.chunkes;
                            MySwingWorker.this.chunkes= new LinkedList<>();
                        }
                        process(list);
                    }
                });
            }
        }
    }

    protected final void setProgress(int progress)
    {
        if(!Thread.currentThread().equals(t))
            throw new UnsupportedOperationException("Must be called from worker Thread!");
        synchronized(syncprogress)
        {
            this.progress=progress;
            if(!progressstate)
            {
                progressstate=true;
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        int value;
                        //Acess Value
                        synchronized(syncprogress)
                        {
                            MySwingWorker.this.progressstate=false;
                            value=MySwingWorker.this.progress;
                        }
                        progress(value);
                    }
                });
            }
        }
    }

    public final synchronized void execute()
    {
        if(!started)
        {
            started=true;
            t.start();
        }
    }

    public final synchronized boolean isRunning()
    {
        return started && !done;
    }

    public final synchronized boolean isDone()
    {
        return done;
    }

    public final synchronized boolean isCancelled()
    {
        return cancelled;
    }

    public final synchronized void cancel()
    {
        if(started && !cancelled && !done)
        {
            cancelled=true;
            if(!Thread.currentThread().equals(t))
                t.interrupt();
        }
    }

}

0

来自Java文档: cancel(boolean mayInterruptIfRunning) "mayInterruptIfRunning - 如果执行此任务的线程应该被中断,则为true;否则,允许正在进行的任务完成"

如果您调用cancel(true)而不是cancel(false),那么它似乎会表现出您所期望的行为。

我没有看到使用EventQueue.isDispatchThread()取消EDT的done()方法被调用。


0

如果你使用return Void:

当doInBackground()执行完毕时,done()方法会被调用。

如果你不使用return Void:

done()方法将被忽略,因为你已经有了返回值,知道任务已经完成。


1
欢迎来到SO。这篇帖子不符合我们的质量标准,如何编写高质量答案请阅读此处 - Rahul Sharma

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