I've been struggling with the usability problem of SwingWorker eating any exceptions thrown in the background task, for example, described on this SO thread. That thread gives a nice description of the problem, but doesn't discuss recovering the original exception.
The applet I've been handed needs to propagate the exception upwards. But I haven't been able to even catch it. I'm using the SimpleSwingWorker wrapper class from this blog entry specifically to try and address this issue. It's a fairly small class but I'll repost it at the end here just for reference.
The calling code looks broadly like
try {
// lots of code here to prepare data, finishing with
SpecialDataHelper helper = new SpecialDataHelper(...stuff...);
helper.execute(); // this will call get+done on the actual worker
} catch (Throwable e) {
// used "Throwable" here in desperation to try and get
// anything at all to match, including unchecked exceptions
//
// no luck, this code is never ever used :-(
}
The wrappers:
class SpecialDataHelper extends SimpleSwingWorker {
public SpecialDataHelper (SpecialData sd) {
this.stuff = etc etc etc;
}
public Void doInBackground() throws Exception {
OurCodeThatThrowsACheckedException(this.stuff);
return null;
}
protected void done() {
// called only when successful
// never reached if there's an error
}
}
The feature of SimpleSwingWorker
is that the actual SwingWorker's done()/get()
methods are automatically called. This, in theory, rethrows any exceptions that happened in the background. In practice, nothing is ever caught, and I don't even know why.
The SimpleSwingWorker class, for reference, and with nothing elided for brevity:
import java.util.concurrent.ExecutionException;
import javax.swing.SwingWorker;
/**
* A drop-in replacement for SwingWorker<Void,Void> but will not silently
* swallow exceptions during background execution.
*
* Taken from http://jonathangiles.net/blog/?p=341 with thanks.
*/
public abstract class SimpleSwingWorker {
private final SwingWorker<Void,Void> worker =
new SwingWorker<Void,Void>() {
@Override
protected Void doInBackground() throws Exception {
SimpleSwingWorker.this.doInBackground();
return null;
}
@Override
protected void done() {
// Exceptions are lost unless get() is called on the
// originating thread. We do so here.
try {
get();
} catch (final InterruptedException ex) {
throw new RuntimeException(ex);
} catch (final ExecutionException ex) {
throw new RuntimeException(ex.getCause());
}
SimpleSwingWorker.this.done();
}
};
public SimpleSwingWorker() {}
protected abstract Void doInBackground() throws Exception;
protected abstract void done();
public void execute() {
worker.execute();
}
}
Forget about your wrapper, which eats the exceptions, whereas the SwingWorker doesn't. Here's how a SwingWorker should be used, and how you should handle a specific exception thrown from the background task:
class MeaningOfLifeFinder extends SwingWorker<String, Object> {
@Override
public String doInBackground() throws SomeException {
return findTheMeaningOfLife();
}
@Override
protected void done() { // called in the EDT. You can update the GUI here, show error dialogs, etc.
try {
String meaningOfLife = get(); // this line can throw InterruptedException or ExecutionException
label.setText(meaningOfLife);
}
catch (ExecutionException e) {
Throwable cause = e.getCause(); // if SomeException was thrown by the background task, it's wrapped into the ExecutionException
if (cause instanceof SomeException) {
// TODO handle SomeException as you want to
}
else { // the wrapped throwable is a runtime exception or an error
// TODO handle any other exception as you want to
}
}
catch (InterruptedException ie) {
// TODO handle the case where the background task was interrupted as you want to
}
}
}