Archived
July 1998
This
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
|