SwingWorker details – canceling background tasks in flight

Posted by

Introduction

f you use the SwingWorker class to run background tasks that don’t freeze up the EDT (Event Dispatch Thread) in your Swing apps, this may be of interest. What happens when you cancel a long running operation that’s running the background? For eg, a user might generate an event that causes a SwingWorker to be created that starts running some code in the background. What happens when the user wants to cancel this long running background operation? This is what we will delve into in this tutorial and get the answer to this question.

You can learn more about threads in this chapter of the Concurrency in Practice book (on Safari Books Online). You can learn more about the Event Dispatch Thread (EDT) in this chapter of the Filthy Rich Clients (on Safari Books Online).

The setup

Let’s say you have some code that you want to run in the background (on a thread that is not the EDT). So this is what you would do. You would subclass SwingWorker and put your code in the doInBackground() method. Here’s an example:

   1: SwingWorker<String, Void> myWorker = new SwingWorker<String, Void>() {
   2:    protected String doInBackground() throws Exception {
   3:      while (!isCancelled()) {
   4:        //run some code in the background…
   5:      }
   6:      return “something”;
   7:    }
   8:    @Override protected void done() {
   9:      try {
  10:        String value = get();
  11:      }
  12:      catch (InterruptedException e) {}
  13:      catch (ExecutionException e) {}
  14:      catch (CancellationException e) {}
  15:    }
  16: };

The first type parameter for our SwingWorker subclass is String, and this defines the type of object that is returned when done() is called… which happens when the background thread completes it’s execution. The second parameter is just Void, since I’m not going to use this SwingWorker to post intermediate results to the EDT for processing (while the background thread is running). Click here for more details on this. Sun’s Java Tutorial has more information on SwingWorker if you need more background information. Note the use of isCancelled() in the while loop… we will cover this in more detail in the sections that follow. The results of this background processing are retrieved on the EDT in the done() method – also note the exceptions, we will cover this in the next sections as well.

To run this snippet, all you have to do is:

   1: myWorker.execute()

To cancel it, all you have to do is:

   1: myWorker.cancel()

The tale of two threads

When you call execute() on myWorker, from the EDT or whatever thread the execute() call is running in, two things happen:

  1. a thread is created that runs the SwingWorker (let’s call this Thread1).
  2. another thread is created by this SwingWorker instance which runs your code in the background (let’s call this Thread2).

Let’s say that you want to cancel the background task because it is taking too long. You would then call cancel() on myWorker. When you call cancel() on the SwingWorker instance myWorker, the following things happen concurrently:

  1. CancellationException gets raised on Thread1 (the SwingWorker thread). So the SwingWorker thread itself jumps out of waiting for doInBackground() method to end, and goes straight into done(). When the get() method is called, this causes a CancellationException to be thrown on the SwingWorker thread itself, and you can catch this in the CancellationException handler. So the SwingWorker thread ends its lifecyle at this point.
  2. InterruptedException gets raised on Thread 2 (the thread that’s actually running your code in the background). If your code is not interruptible, or if you catch the InterruptedException and just keep going, then this thread will not die, and will continue doing it’s background processing! This is why it’s necessary to check to see if isCancelled() is true. This is the only way (outside of responding to an InterruptedException) that can cause the background thread to stop running your code. Also, when your background task completes execution and it returns the String, nothing will happen, since the SwingWorker (Thread1) that was supposed to respond to this (in it’s done() method) is already dead. If you use call sleep() or wait(), then these methods will respond to an InterruptedException being raised, otherwise, the only way to tell is by checking isCancelled(). So it’s pretty easy, if you’re not careful, for the underlying thread executing your background code and the SwingWorker thread itself to get out of “sync”. Also, if you have code that’s doing some network IO, you have to use an InputStream or OutputStream that can check the isCancelled() method to break the IO operation. If you can’t do this, then you can try closing the underlying IO streams and causing an IOException to occur when isCancelled() is detected.

Closing thoughts

In your Swing apps that use SwingWorker to perform lengthy background tasks, it’s necessary to keep in mind that just because you called cancel() on the SwingWorker doing your task in the background that it’s been “cancelled”. Java does not have preemptive multithreading – only cooperative. So it’s your onus to check the isCancelled() method in your doInBackground() code, and do the proper exception handling in the done() method of the SwingWoker to make sure that you don’t have a thread leak. Also, it’s important to process results from your background operation in the done() method – this will ensure that the 2 threads won’t go out of “sync”. Since the SwingWorker thread can be cancelled without the underlying execution thread knowing, it’s important to perform any changes to your system in the done() method – if the SwingWorker get’s cancelled, then these changes won’t show up in your system.