|
Using a Swing Worker Thread
New Ways to Perform Background Tasks
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: In September 2000 we changed
this article and its examples to use an updated version of SwingWorker
that fixes a subtle threading bug.
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 a 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. You then instantiate the class and
invoke the start()
method on the new instance.
For example, the following code snippet spawns
a thread 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";
}
};
worker.start();
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 was that once a worker thread was started, you couldn'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 interrupt() Method
The second version of SwingWorker introduced
a method called interrupt()
that enables interrupting the thread. Your thread can be notified
of the interrupt in one of two ways:
- Your thread is executing a method such
as sleep()
or wait()
that can throw an InterruptedException when the call to interrupt()
takes place.
- 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 the interrupted()
method.
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. For
example:
Then execute the application. For example:
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
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 show you how to use the interrupt()
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.
Once started, 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.
We'd like to thank Doug Lea (author of "Concurrent
Programming in Java"), Joseph Bowbeer, and the other readers who sent
us feedback on the threads articles and SwingWorker class.
-- Hans Muller and Kathy
Walrath
|