[an error occurred while processing this directive]
The Morgue Commentary PLAF Papers Friends Tech Topics Swing Text The Web Tech Topics What's Swing?
Index Archive Call 911 PLAF Papers Friends Tech Topics Swing Text Swing & the Web IDE Roundup Special Report Databank Page 2 Page One What's Swing?
JDK Docs Download JDK Swing API Docs Download Swing Java Tutorial
The Swing Connection The Archive Tech Topics

Archived July 1998  

head_main_swing_worker 

Needle and Thread graphicThis article gives examples of using the SwingWorker class. The purpose of SwingWorker is to implement a background thread that you can use to perform time-consuming operations without affecting the performance of your program's GUI. For basic information about SwingWorker, see Threads and Swing.



NOTE: The SwingWorker class featured in this article has been revised to use the Swing 1.1 package names and to incorporate feedback from several readers, including Joseph Bowbeer and Doug Lea (author of "Concurrent Programming in Java"). The feedback helped us correct the following problems of the first versions of SwingWorker:

  • If the SwingWorker's construct method threw an uncaught exception, a call to SwingWorker.get() could hang.
  • In a few cases, two threads could effectively see an inconsistent value in the SwingWorker.thread field.

We'd like to thank Joe, Doug, and the other readers who sent us feedback on the threads articles for their thoughtful reviews.


Threads are essential in Swing programs that perform user-initiated operations that are time-consuming or can block. For example, if your application makes a database request or loads a file in response to the user selecting a menu item, then you should do the work in a separate thread. This article describes one approach for spinning off a worker thread to do this.

Here's a summary of what this article contains:


Overview: The SwingWorker Class

Because SwingWorker isn't part of the Swing release, you need to download and compile its source code to be able to use it. Here it is:

Review of SwingWorker

The SwingWorker class is a simple utility for computing a value on a new thread. To use it, you create a subclass of SwingWorker that overrides the SwingWorker.construct() method to compute the value.

For example, the following code snippet spawns a thread (by instantiating SwingWorker) that constructs a string. Next, the snippet uses the get() method to get the value returned by the construct method, waiting if necessary. Finally, it displays the value of the string.


    SwingWorker worker = new SwingWorker() {
       public Object construct() {
          return "Hello" + " " + "World";
       }
    };
    System.out.println(worker.get().toString());

In a real application, the construct method would do something useful (but potentially time-consuming). For example, it might do one of the following:

  • Perform extensive calculations
  • Execute code that might result in many classes being loaded
  • Block for network or disk I/O
  • Wait for some other resource

A SwingWorker feature not shown by the above snippet is that once construct() returns, SwingWorker lets you execute code on the event dispatching thread. You do this by overriding SwingWorker's finished method. Typically, you might use finished to display a component hierarchy you've just constructed or to set the data displayed by one or more components.

One limitation of the original SwingWorker class is that once a worker thread is started, you can't interrupt it. However, it's rather bad style for an interactive application to make the user wait while a worker thread finishes. If the user wants stop an operation that's in progress, the worker thread that's busy performing the operation should be interrupted as quickly as possible.

Using the New SwingWorker

The enhanced version of SwingWorker has an additional method called interrupt() that enables interrupting the thread. If you're interested, you can read a short discussion of the interrupt() method's implementation.

Here's how and when your thread can be notified of the interrupt:

  • When the call to interrupt() takes place, your thread is executing a method such as sleep() or wait() that can throw an InterruptedException.
     
  • Your thread explicitly asks whether it has been interrupted, using code like this:

    Thread.interrupted()

In worker threads that use the sleep() or wait() method (such as the threads in the following examples), there's usually no need to explicitly check for interrupts. It's generally sufficient to let the sleep() or wait() method throw an InterruptedException.

However, if you want to be able to interrupt a SwingWorker that doesn't contain a timed loop, then the SwingWorker needs to check for interrupts explicitly with interrupted().


Introduction to the Worker Thread Examples

The rest of this article features an application that contains two worker thread examples. Here's a picture of the application's main window, along with a dialog it brings up:

 

 

The example's source code consists of these files:

You can download all these files as a single zip file. The zip file also includes a more impressive (but more complex) version that displays HTML help for each example.

To run the example, first compile it, making sure that the 1.1

swing.jar file is in your class path. For example:

    /usr/local/java/jdk1.1.7/bin/javac -classpath .:
    /usr/local/java/swing-1.1/swing.jar:
    /usr/local/java/jdk1.1.7/lib/classes.zip
    ThreadsExample.java

Then execute the application. For example:

    /usr/local/java/jdk1.1.7/bin/java -classpath .:
    /usr/local/java/swing-1.1/swing.jar:
    /usr/local/java/jdk1.1.7/lib/classes.zip
    ThreadsExample

When you click a Start button, the relevant example's worker thread is created. You can see its progress reflected in the progress bar. You can click Cancel to interrupt the worker thread. A few seconds after starting Example 2, you'll get a dialog that asks you to confirm whether to proceed. It's described in detail later.


Example 1: Interrupting a Swing Worker Thread

To show you how to use the new

SwingWorker class, this section features an example called Example1.java. The worker thread in this example contains a loop that executes 100 times, sleeping for half a second each time.


    //progressBar maximum is NUMLOOPS (100)
    for(int i = 0; i < NUMLOOPS; i++) {
       updateStatus(i);
       ...
       Thread.sleep(500);
    }

To demonstrate the SwingWorkerinterrupt method, we've created an application that lets you start a SwingWorker and then either wait for it to complete or interrupt it. In this application's subclass of SwingWorker, the only thing the construct method does is to call an Example1 method called doWork(). The complete source code for the doWork() method follows. The example handles interruptions of the worker thread by resetting the progress bar and setting the label to "Interrupted". Because an interruption might occur outside of the sleep() method call, the code checks for interruptions before calling sleep().


    Object doWork() {
       try {
          for(int i = 0; i < NUMLOOPS; i++) {
             updateStatus(i);
             if (Thread.interrupted()) {
                throw new InterruptedException();
             }
             Thread.sleep(500);
          }
       }
       catch (InterruptedException e) {
          updateStatus(0);
          return "Interrupted"; 
       }
       return "All Done";
    }

This method does what any time-consuming operation should: It periodically lets the user know that it's making progress. The updateStatus() method queues a Runnable object for the event-dispatching thread (remember: don't do GUI work on other threads) to update a progress bar. When the Start button is pressed, the action listener creates the SwingWorker, which results in the worker thread being created. The worker thread executes its construct() method, which (as the following code shows) calls doWork(). Here is the code that implements and instantiates Example1's subclass of SwingWorker.


    worker = new SwingWorker() {
       public Object construct() {
          return doWork();
       }
       public void finished() {
          startButton.setEnabled(true);
          interruptButton.setEnabled(false);
          statusField.setText(get().toString());
       }
    };

The finished method runs after the construct() method returns (when the worker thread is finished). It simply reenables the Start button, disables the Cancel button, and sets the status field to the value computed by the worker. Remember that the finished method runs on the event dispatching thread, so it can safely update the GUI directly.


Example 2: Prompting the User from a Worker Thread

This example is implemented as a subclass of Example1. The only difference is that after the worker thread has run for about two seconds, it blocks until the user has responded to a Continue/Cancel modal dialog. If the user doesn't choose Continue, we exit the doWork() loop.

This example demonstrates an idiom common to many worker threads: If the worker runs into an unexpected condition it may need to block until it has alerted the user or collected more information from the user with a modal dialog. Doing so is a little complex because the dialog needs to be shown on the event-dispatching thread, and the worker thread needs to be blocked until the user has dismissed the modal dialog.

We use the SwingUtilities method invokeAndWait() to pop up the dialog on the event-dispatching thread. Unlike invokeLater(), invokeAndWait() blocks until its Runnable object returns. In this case, the Runnable object will not return until the dialog has been dismissed. We create an inner Runnable class, DoShowDialog, to handle popping up the dialog. An instance variable, DoShowDialog.proceedConfirmed(), records the user's response:


    class DoShowDialog implements Runnable {
       boolean proceedConfirmed;
       public void run() {
          Object[] options = {"Continue", "Cancel"};
             int n = JOptionPane.showOptionDialog
             (Example2.this,
             Example2: Continue?",
             "Example2",
             JOptionPane.YES_NO_OPTION,
             OptionPane.QUESTION_MESSAGE,
                null,
                options,
                "Continue");
             proceedConfirmed =
                (n == JOptionPane.YES_OPTION);
       }
    }

Because the showConfirmDialog() method pops up a modal dialog, the call blocks until the user dismisses the dialog.

To show the dialog and block the calling thread (the worker thread) until the dialog's dismissed, the worker thread calls the invokeAndWait() method, specifying an instance of DoShowDialog:


    DoShowDialog doShowDialog = new DoShowDialog();
    try {
       SwingUtilities.invokeAndWait(doShowDialog);
    }
    catch
       (java.lang.reflect.
          InvocationTargetException e) {
          e.printStackTrace();
    }

The code catches the InvocationTargetException as a relic of debugging DoShowDialog's run() method. Once the invokeAndWait() method has returned, the worker thread can read doShowDialog.proceedConfirmed() to get the user's response.

-- Hans Muller and Kathy Walrath

[an error occurred while processing this directive]