Javadoc of the done()
method of SwingWorker:
Executed on the Event Dispatch Thread after the doInBackground method is finished.
I've clues that it is not true in the case of canceled worker.Done
is called in each case (normal termination or cancellation) but when cancelled
it is not enqueued to the EDT, as it happens for normal termination.
Is there some more precise analisis on when done
is called in the case that a SwingWorker
is cancelled?
Clarification:
this question is NOT on how to cancel
a SwingWorker
. Here it is assumed that the SwingWorker
is cancelled in the right way.
And it is NOT about thread still working when they are supposed to be finished.
When a thread is cancelled via
myWorkerThread.cancel(true/false);
the done method is (quite surprisingly) called by the cancel method itself.
What you may expect to happen, but actually DOESN'T:
- you call cancel (either with mayInterrupt or not)
- cancel set up the thread cancellation
- the doInBackground exits
- the done is called*
(* the done is enqueued to the EDT, that means, if EDT is busy it happens AFTER the EDT has finished what it is doing)
What actually DOES happen:
- you call cancel (either with mayInterrupt or not)
- cancel set up the thread cancellation
- the done is called as part of cancel code*
- the doInBackground will exit when it will have finished its loop
(*the done isn't enqueued to the EDT, but called into the cancel call and so it has a very immediate effect on EDT, that often is the GUI)
I provide a simple example that proves this.
Copy, paste and run.
1. I generate a runtime exception inside done. The stack thread shows that done is called by cancel.
2. About after 4 seconds after cancelation, you'll recive a greeting from the doInBackground, that fhurterly proves that done is called before the thread exiting.
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!");}
}
}
done()
is called in any case, wether the worker is cancelled or it finishes normally. Nevertheless there are cases where the doInBackground
is still running and the done
method is called already (this is done inside cancel()
no matter if the thread already finished). A simple example can be found here:
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();
}
Thus it can be the case that done
is called before doInBackground
finishes.
something is possible, other could be illusion
really nice outPut
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)
from
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 ");
}
}
}
}
Until the SwingWorker is fixed http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6826514 Here a simple (tested) version with the basic (similar) functions then 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();
}
}
}
From the Java docs: cancel(boolean mayInterruptIfRunning) "mayInterruptIfRunning - true if the thread executing this task should be interrupted; otherwise, in-progress tasks are allowed to complete"
If you call cancel(true) instead of cancel(false) that seems to behave as you are expecting.
I have not seen done() called off the EDT using EventQueue.isDispatchThread()
IF you use return Void: ...@Override public Void doInBackground(){...
done() is invoked when doInBackground() has finished.
IF you don't use return Void: ...@Override public boolean doInBackground(){...
done() is ignored and you know have finished cause you have your return.
来源:https://stackoverflow.com/questions/6204141/swingworker-when-exactly-is-called-done-method