24

I use SwingWorker in Java 6 to avoid running long-running code on the event dispatch thread.

If the call to get() in my done() method returns an exception, what is an appropriate way of handling the exception?

I'm particularly concerned about possible InterruptedExceptions. The JavaDoc example simply ignores the exception but I've learnt over the years that swallowing exceptions leads to hard-to-debug code.

A sample usage is as follows:

new SwingWorker<String, Void>() {

    @Override
    protected String doInBackground() throws Exception {
        // do long-running calculation
        return result;
    }

    @Override
    protected void done() {
        try {
            setTextField(get());
        } catch (InterruptedException e) {
            e.printStackTrace();  
        } catch (ExecutionException e) {
            e.printStackTrace();  
        }
    }
}.execute();
jjnguy
  • 136,852
  • 53
  • 295
  • 323
Steve McLeod
  • 51,737
  • 47
  • 128
  • 184

7 Answers7

12

It's an old post, but I want to do some clarification:

SwingWorker.get throws InterruptedException, ExecutionException as checked exceptions.

Plus it throws a very specific unchecked exception that is CancellationException. Of course it could potentially throw any unchecked exception, but CancellationException is not an "exceptional" and unexpected one. It is thrown when you try to call get method after that has been called cancel.

ExecutedException is thrown when an Exception is thrown inside doInBackground. The original exception is wrapped inside an ExecutionException. When the get() method will be called, the ExecutionException will be thrown. The idea of take out the original exception and manage that is good. (As Emil H pointed out).

CancellationException is unchecked and in my opinion should be checked. The only excuse for the API implementation not to have it as checked is that it has a status method isCancelled().
You can either:
- test isCancelled() and if is true do NOT call get() since it will throw CancellationException
- surround get() with try-catch and add the CancellationException, that since unchecked will not requested by compiler
- the fact that CancellationException is not checked leave you free to forget all that stuff and get a nice surprise.
- do anything because you won't cancel the worker

InterruptedException. If you cancel a SwingThread with cancel(true) the first interruptable method call (for sure Thread.sleep, this.wait, maybe some IO methods) in doInBackground will throw InterruptException. But this exception is not wrapped in an ExecuteException. The doInBackground is left to terminate with interrupted exception. If it is catched and converted to some other exception, those will be ignored because by this moment cancel has already invoked SwingThread.done on the EDT, and if done has called get, it has get just the standard CancellationException. Not an InterruptedException!

If you cancel with cancel(false) no InterruptException is raised inside doInBackground. The same if you cancel with cancel(true) but there are no interruptable method calls inside doInBackground. In these cases, the doInBackground will follow its natural loop. This loop should test the isCancelled method and exit gracefully. If the doInBackground doesn't do so, it will run forever. I've not tested for the presence of timeouts, but I not belive so.

For me, it remains just a gray area. In which cases is InterruptedException thrown by get? I'd like to see some short code, since I couldn't produce a similar exception. :-)

P.S. I've documented in another Question&Answer that the done and state change listener in case of cancellation are called before the doInBackground exits. Since this is true, this -that is not a bug- requires a special attention when designing doInBackground method. If you are intrested in this, see SwingWorker: when exactly is called done method?

Community
  • 1
  • 1
AgostinoX
  • 7,477
  • 20
  • 77
  • 137
3

It depends very much on the type of errors that might result from the background job. If the job in doInBackground throws an exception, it will be propagated to the done method as a nested ExecutionException. The best practice in this case would be to handle the nested exception, rather than the ExecutionException itself.

For example: If the worker thread throws an exception indicating that the database connection has been lost, you'd probably want to reconnect and restart the job. If the work to be done depends on some kind of resource that turns out to already be in use, it would be a good idea to offer the use a retry or cancel choice. If the exception thrown doesn't have any implications for the user, just log the error and continue.

From what I can remember, I believe that the InterruptedException won't be an issue here since you make the get method call in the done method, since the InterruptedException will only be thrown if the get call is interrupted while waiting for the background job to finish. If an unexpected event like that were to occur you'd probably want to display an error message and exit the application.

Emil H
  • 39,840
  • 10
  • 78
  • 97
2

This is as much an interface question as it is an error handling question. A lot of apps add some small table that lists the running background jobs. An exception in one of them might flash the table row that produced an error, or do something as disruptive as present an alert. It depends on the severity of the exception. I think the harder question you'll probably have to answer is how many potentially different types of exceptions are we talking about, and what is their relative severity.

I think a simple compromise might be to present a modal alert for your most severe errors, and anything else, simply record the occurrence until it a) blocks the user from proceeding furhter or b) the user closes the document/window, at which time you can show a list of exceptions that happened during background processing tasks at the same time that you ask if you want to save any unsaved buffers for example.

stinkymatt
  • 1,658
  • 11
  • 14
2

What I would recommend is to let the error propagate all the way up to where the action was started.

For example, if a user clicks on a button to fetch data from a data-source. If a problem occurs, whether it being a credential error, a network error, a database error, or whatever else, it wouldn't be appropriate for you to have logic within that worker thread to try to solve it right there.

But if you let it propagate to where the task was started, from there you can take the appropriate error corrections, such as popping the credentials dialog once more, showing a "try again" dialog or even showing an error message.

Jeach
  • 8,656
  • 7
  • 45
  • 58
  • 2
    How would I do this, seeing as the SwingWorker could throw the exception at a later stage? The action handler invoked by the button would have already returned. – Steve McLeod May 02 '09 at 12:30
  • 1
    I have had problems with the Exception never being thrown. I have read that this is due to the way the Exception is caught in the SwingWorker internals, and that it may be thrown later, and it may not. I work around this by setting an Exception field in my SwingWorker implementations, and checking to see if it is null when the SwingWorker is finished. – Jeremy Brooks Nov 09 '13 at 03:33
0

I should've clarified my original post. Don't simply catch certain Exception types in the done() method. Any code that executes in the doInBackground() could throw any type of Exception and only catching the Exceptions in your question can lead to Exceptions being thrown on the EDT (Event Dispatch Thread or main GUI thread). Catching all Exceptions types in the done() method is just a good habit to have when using SwingWorkers.

@Override
protected void done()
{
    try
    {
        if(!super.isCancelled())
        {
            super.get();
        }
    }
    catch(Exception ex)
    {
        ex.printStackTrace();
    }
}
jdb1015
  • 127
  • 6
0

Assume this is a GUI application, you may want to provide a visual feedback about failed operation upon exception.

Journeyman Programmer
  • 1,346
  • 1
  • 9
  • 15
-2

I guess you don't get many of these question with C#. You need to understand what the exception is and deal with it appropriately (which is usually to let it go further up the stack).

InterruptedException - Thrown when a thread is interrupted (by Thread.interrupt) when waiting (roughly). Why would you want a thread to be interrupted? Usually you want the thread to stop what it's doing - reset the interrupt state and exit. For instance, the PlugIn will interrupt applet threads if they continue for much longer after an applet should be gone. However, in this case provided done is called correctly you shouldn't be waiting at all. Therefore, it would be appropriate to wrap the exception in an IllegalStateException (and the API docs should probably state that). It's a really bad API. The publish/process mode probably makes more sense.

ExecutionException - You need to deal with the wrapped exception. If you are not expecting a particular type of exception, wrap it in an unchecked exception.

Generally I would suggest a clear separation between what happens on the EDT and what happens off the EDT. Therefore, avoid SwingWorker in production code.

Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
  • 1
    Why avoid SwingWorker in production code? Sun promotes it http://java.sun.com/docs/books/tutorial/uiswing/concurrency/index.html – Eddie Apr 27 '09 at 04:35
  • Avoid SwingWorker because it is atrocious design. Don't use something just because a company tells you to. – Tom Hawtin - tackline Apr 27 '09 at 08:41
  • This answers my question perfectly: "Therefore, it would be appropriate to wrap the [InterruptedException] in an IllegalStateException (and the API docs should probably state that)" – Steve McLeod Apr 28 '09 at 13:30
  • Alternatives to SwingWorker: java.util.concurrent.ThreadPoolExecutor for running off EDT, java.awt.EventQueue.invokeLater for running on EDT. In some cases you might want to be a bit smarter with invokeLater, taking more than one item at a time (but only if that is determined to be necessary). – Tom Hawtin - tackline Apr 28 '09 at 13:41
  • 4
    Tom, the new framework in Java 6 was implemented in order to add greater flexibility to the existing framework. For example, in Java 6 you can actually populate a JTable with massive amount of data as the data is being loaded. Although it should be used when appropriate its kind of far fetch to recommend to avoid it. None of your suggested alternatives can do what the Java 6 was designed for. – Jeach May 02 '09 at 05:50
  • 1
    You could always retrieve data in the background (although in Java 1.1 there was no EventQueue.invokeLater). SwingWorker allows trivial examples to be written with few lines of code. Real code tends to get more complex very quickly. SwingWorker imposes a bad design that tightly couple EDT and non-EDT work. – Tom Hawtin - tackline May 02 '09 at 09:50
  • 2
    I think you are wrong. You do want to know if the thread was interrupted. For example, the background task got canceled. Thus, when done is called, you won't be able to retrieve the result, but instead you get an InterruptedException. It's not an illegal state. It is legal for someone to cancel the background task, and you have to deal with it however it feels appropriate. I think in the catch clause for the InterruptedException, you should write the handling code considering you were not able to retrieve the data the task was supposed to fetch. – Andrei Vajna II Sep 03 '09 at 14:26