|
Articles Index
Contents
One common mistake of desktop application programmers is misusing
the Swing event dispatch thread (EDT). They either unknowingly
access user interface (UI) components from non-UI threads or simply
disregard the consequences. The result is that applications become
unresponsive or sluggish because they perform long-running
tasks on the EDT instead of on separate worker threads. Long-running
computations or input/output (I/O) bound tasks should never run on
the Swing EDT. Finding problematic code may not
always be simple, but the Java
Platform, Standard Edition 6 (Java SE 6) makes it easier to fix
such code by providing the javax.swing.SwingWorker
class.
This article describes how to avoid slow, sluggish, or
unresponsive UIs by using the SwingWorker class to
create and manage worker threads in a demo application called Image
Search. This application demonstrates how to correctly use the
SwingWorker application programming interface (API) by
interacting with the popular Flickr web site to search and
download images. You can download this demo application and its
source code at the end of this article.
To get a basic understanding of Swing UI concepts, including
event handlers and listeners, and learn more about UI programming,
follow the Java Tutorial's Swing
trail.
Introducing the Demo Application
The Image Search demo performs a long-running task that should
not execute on the EDT -- accessing a Flickr web
service. The Image Search application searches the Flickr site for
images that match a user-supplied search term, downloads matching
thumbnail images, and downloads a larger image if the
user selects its thumbnail from a list. Instead of executing the
tasks on the EDT, the application uses the SwingWorker class to
perform the tasks as worker threads.
When the user types in a search term, the application initiates
an image search on the Flickr web site. If any images match the
search term, the application downloads a list of up to 100 matching
thumbnail images and produces a selectable list. You can modify the
application to download more or fewer images. A progress bar
tracks the image search. Figure 1 shows the
search field and its progress bar.
Figure 1. Search for images and see the download progress.
|
As the application retrieves thumbnail images, it puts them in a
JList component. Matching images populate the list as
they arrive from the Flickr site. Using a SwingWorker
instance, the application can put individual images in the list as
they arrive instead of waiting for the entire list. Figure 2 shows
images in the list.
Figure 2. A list holds matching thumbnail images.
|
When you select an image from the list, the application downloads a
larger version of the image and displays it below the
list. Another progress bar tracks the larger image's download.
Figure 3 shows a list selection and the progress bar as the larger
image arrives.
Figure 3. Download a larger image by selecting a thumbnail.
|
Finally, after the entire image downloads, the application
displays it under the list.
The application uses SwingWorker for all image
search and download tasks. Additionally, the demo shows you how to
cancel tasks and how to get intermediate results before the complete
task has finished. The application has two SwingWorker
subclasses: ImageSearcher and
ImageRetriever. The ImageSearcher class is
responsible for searching and retrieving thumbnail images for the
UI's list. The ImageRetriever class is responsible for
retrieving a larger version of the image when a user selects it from
the list. This article will use both subclasses to describe all the
major features of the SwingWorker class. Figure 4 shows
the complete application showing the search term, progress bars, the
image list, and the selected image.
Figure 4. The SwingWorker class helps create a responsive image search application.
|
Reviewing Swing Thread Basics
Swing applications have three types of threads:
- An initial thread
- A UI event dispatch thread (EDT)
- Worker threads
Every application must have a main method that
represents its starting point. This method runs on an initial or
startup thread. The initial thread might read program arguments and
initiate a few other objects, but in many Swing applications, this
thread's primary purpose is to start the application's graphical
user interface (GUI). Once the GUI starts for most event-driven
desktop applications, the initial thread's work is done.
Swing applications have a single EDT for the UI. This thread
draws GUI components, updates them, and responds to user
interactions by calling the application's event handlers. All event
handlers run on the EDT, and you should programmatically interact
with your UI components and their basic data models only on the EDT.
Any tasks running on the EDT should finish quickly so that your UI
is responsive to user input. Accessing your UI
components or their event handlers from other threads will cause
update and drawing errors in the UI. Performing long-running tasks on the EDT will cause
your application to become unresponsive because GUI events will
accumulate in the event dispatch queue.
Finally, worker threads should perform long-running calculations
or input/output (I/O) bound tasks. You should use worker threads for
tasks such as communicating with databases, accessing web resources,
and reading or writing large files. Anything that might interfere
with or delay UI event handling should exist in a worker thread.
Also, interacting with Swing components or their default data models
from initial or worker threads is not a safe operation.
The SwingWorker class helps you manage the interaction between
your worker threads and the Swing EDT. Although SwingWorker doesn't
solve all the problems that you might experience while working with
concurrent threads, it does help you to separate Swing and worker
threads and to use both for their intended purposes. For the EDT,
that purpose is to draw and update the UI while responding to user
interactions. For worker threads, that purpose is to perform I/O
bound or other long tasks that are not directly related to the UI.
Starting off on the Right Thread
An initial thread runs your application's main
method. This method can perform numerous tasks, but in a typical
Swing application, its last and major task is to create and run the
application's UI. The point of UI creation, the point at which your
application basically hands control over to your UI, is often the
source of your application's first problem interacting with the EDT.
The Image Search demo starts within its MainFrame
class. Many applications start their UI as follows,
but this is not the right way to start the UI:
public class MainFrame extends javax.swing.JFrame {
...
public static void main(String[] args) {
new MainFrame().setVisible(true);
}
}
|
Although this mistake appears benign, it still violates the rule
that you should not interact with Swing components from any thread
except the EDT. This particular mistake is easy to make, and thread
synchronization problems may not be immediately obvious. However,
you should still avoid it.
Here is the correct way to instantiate your application's UI:
public class MainFrame extends javax.swing.JFrame {
...
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new MainFrame().setVisible(true);
}
});
}
}
|
Not directly related to SwingWorker but useful nevertheless, the
javax.swing.SwingUtilities class
contains a collection of static methods that help you interact with
UI components. It includes the invokeLater method that
puts a Runnable task on the EDT. The
Runnable interface defines a task that can be executed
as a thread
Use the invokeLater method to correctly instantiate
your application UI from the initial thread. The method is
asynchronous, which means that the call will return immediately to
the calling thread. After creating the UI, however, many initial
threads have little or nothing further to do.
Here are two common ways to call this method:
SwingUtilities.invokeLater
EventQueue.invokeLater
Either of these method calls is correct, so choose whichever you
prefer. In fact, the SwingUtilities version is just a
thin wrapper method that calls the EventQueue.invokeLater method. Because the Swing
framework itself uses SwingUtilities frequently, your
use of SwingUtilities does not pull any extra class
code into your application.
Another way to put tasks on the EDT is to use the
SwingUtilities.invokeAndWait method. Unlike the
invokeLater method, the invokeAndWait
method is synchronous. It will execute the Runnable
task on the EDT, but it will not return to the caller until the task
has finished.
Both invokeLater and invokeAndWait will
execute their Runnable task after all other tasks on
the event dispatch queue have been processed. In other words, these
methods put the Runnable task at the end of the
existing queue. Although you can use invokeLater from
either your application or EDT, you should never use the invokeAndWait method from that thread. Using invokeAndWait from the EDT would create the same response delays that you should avoid.
Limiting the EDT to GUI Work
The Swing framework manages component drawing, updates, and event handlers on the EDT. As you might imagine, the event queue for this thread is busy because every GUI interaction and event passes through it. Tasks on the event queue must complete quickly, or they will prevent other tasks from running. The effect is an unresponsive GUI that gets choked with a backlog of waiting events, causing the GUI to become sluggish and unappealing to the user. Ideally, any task that requires more than 30 to 100 milliseconds should not run on the EDT. Otherwise, users will sense a pause between their input and the UI response.
Fortunately, Swing performance doesn't have to degrade just because you have complex tasks, computations, or I/O bound operations that must execute as a result of a GUI event. After all, many desktop applications perform long-running tasks such as solving spreadsheet formulas, issuing database queries across the network, or sending messages to other applications across the Internet. Despite these tasks, your UI can remain snappy and responsive to the user. Maintaining a responsive application involves creating and managing separate threads that can run independently
from the EDT.
Two event handlers in the Image Search application will slow UI responsiveness if they complete on the EDT: the image search handler and the selected image download handler.
Both of these handlers access a web service, which can take many seconds to respond. During that time, if the application performs
the web service interactions on the EDT, the user will not be able to cancel the search or interact with the GUI. These methods and others like them should not run on the EDT.
Figure 5 shows an EDT that cannot process UI events between points A and B, which represent the span of time for executing I/O bound queries to the Flickr web service.
Figure 5. The EDT cannot respond to UI events while executing the web service query.
|
The javax.swing.SwingWorker class is new in the Java SE 6 platform. Using SwingWorker, your application can launch a worker thread to perform the queries asynchronously and quickly return to business on the EDT. Figure 6 shows the shorter time between points A and B, meaning that the EDT is able to continue processing UI events without a long delay.
Figure 6. Using a worker thread, your application can remove I/O bound tasks from the EDT.
|
Forging Ahead With SwingWorker Fundamentals
This section provides a quick tour of SwingWorker functionality. The
SwingWorker documentation describes the class
this way:
public abstract class SwingWorker<T,V> extends Object
implements RunnableFuture
|
The SwingWorker class is abstract, so you must subclass it to
perform the specific task you need. Notice that the class has type parameters T and V. The T type
indicates that an implementation's doInBackground and
get methods will return values of T type. The V type indicates that an implementation's
publish and process methods will operate
on values of type V. You will find detailed descriptions of these methods later in this article.
The class also implements the
java.util.concurrent.RunnableFuture interface. The RunnableFuture interface is really nothing more than a wrapper for two separate interfaces: Runnable and Future.
Because it is Runnable, a SwingWorker implementation
has a run method. Runnable objects execute as part of a thread. A Thread object will invoke the
run method when it starts.
Because it is a Future type, a SwingWorker will provide its execution results in a value of type T and will provide a way to
interact with the thread. The SwingWorker class implements the following interface methods:
boolean cancel(boolean mayInterruptIfRunning)
T get()
T get(long timeout, TimeUnit unit)
boolean isCancelled()
boolean isDone()
The SwingWorker class implements all of these interface methods for you. In fact, the only method you really must override is the following abstract SwingWorker method:
protected T doInBackground() throws Exception
|
The doInBackground method runs as part of the
worker thread. It performs the primary task of the thread, and it
must provide the thread's results in its return value. Override this
method and make sure it contains or delegates the thread's primary
task. You should not call this method directly. Instead, use the
worker object's execute method to schedule execution.
Use the worker's get method to retrieve the results
of the doInBackground method. Although you can call the
get method from the EDT, this method will block until
the worker thread is done. You should call this method only
when you know the results are available so that the user will not
have to wait for results. To avoid blocking, you can also use the
isDone method to check whether the
doInBackground method has finished. Also, an overloaded
get(long timeout, TimeUnit unit) method will wait up to
the alloted time for the thread to complete before returning the
results.
Perhaps a better location for retrieving the worker object's results is
from within the done method:
SwingWorker calls this method after the doInBackground method finishes. Override the done method if the worker object needs to update a GUI component with the results of the thread or to clean up. This is a
good place to call the get method because you know that the thread's work is finished when this method executes. SwingWorker invokes the done method on the EDT, so you
can safely interact with any GUI components from within this method.
You don't have to wait until the thread completes before getting intermediate results. Intermediate results are data chunks
that a worker thread can produce before providing a final result. As the worker thread executes, it can publish results of V type. Override the process method to work with
intermediate results. You will find more detail about these methods later in this article.
A SwingWorker instance can notify listeners when its properties change. A SwingWorker instance has two important properties: state and progress.
A worker thread has several states, represented by the following
SwingWorker.StateValue enumeration values:
A worker thread is in the
PENDING state immediately after its creation. When the doInBackground method begins, the worker thread enters the STARTED state. A worker thread is in the DONE state after its doInBackground method
finishes. The SwingWorker superclass sets these state values automatically as it moves through its life cycle. You can add listeners that receive notification when
this property changes.
Finally, a worker object has a progress property. As the worker progresses, it can update this property with integer values from 0 through 100. The worker can notify listeners when this property changes.
Implementing a Simple ImageRetriever
When you click on a thumbnail in the list, an event handler creates an ImageRetriever instance and executes it. The ImageRetriever class downloads a selected image from the thumbnail list and displays it below the list.
When implementing a SwingWorker subclass, you must specify the return value type of the doInBackground and
get methods. These methods return an Icon
object in the ImageRetriever implementation. Because ImageRetriever
does not produce any intermediate results, it uses the special Void type to indicate that fact. The following code shows most of the ImageRetriever implementation:
public class ImageRetriever extends SwingWorker<Icon, Void> {
private ImageRetriever() {}
public ImageRetriever(JLabel lblImage, String strImageUrl) {
this.strImageUrl = strImageUrl;
this.lblImage = lblImage;
}
@Override
protected Icon doInBackground() throws Exception {
Icon icon = retrieveImage(strImageUrl);
return icon;
}
private Icon retrieveImage(String strImageUrl)
throws MalformedURLException, IOException {
InputStream is = null;
URL imgUrl = null;
imgUrl = new URL(strImageUrl);
is = imgUrl.openStream();
ImageInputStream iis = ImageIO.createImageInputStream(is);
Iterator<ImageReader> it =
ImageIO.getImageReadersBySuffix("jpg");
ImageReader reader = it.next();
reader.setInput(iis);
...
Image image = reader.read(0);
Icon icon = new ImageIcon(image);
return icon;
}
@Override
protected void done() {
Icon icon = null;
String text = null;
try {
icon = get();
} catch (Exception ignore) {
ignore.printStackTrace();
text = "Image unavailable";
}
lblImage.setIcon(icon);
lblImage.setText(text);
}
private String strImageUrl;
private JLabel lblImage;
}
|
Because the ImageRetriever class will download an image and place
it on a large label, providing the label and image URL in the
constructor is convenient. ImageRetriever needs the URL to retrieve
an image. Provide the label so that the ImageRetriever instance can
set the label's icon itself. If you use inner classes, you might not
even provide this information in the constructor, because the worker
thread will be able to access the information directly. However,
providing the information in the constructor helps your application
to be more thread-safe because that information will not be shared
among ImageRetriever instances.
Notice how this class specifies the Icon return type
for the doInBackground and get methods.
The class specifies the Void type for intermediate
results because it does not produce any.
public class ImageRetriever extends SwingWorker<Icon, Void>
|
In this implementation, the doInBackground method
must conform to the class contract by returning an Icon
object. By indicating the Icon type in the class
definition, you are telling the compiler that both the
doInBackground and get methods will return
an Icon object. You cannot override the
get method, because its default implementation is
declared with the final keyword.
The doInBackground method retrieves an image from
the URL provided in the class constructor, and it produces an Icon
result:
@Override
protected Icon doInBackground() throws Exception {
Icon icon = retrieveImage(strImageUrl);
return icon;
|
When the doInBackground method completes,
SwingWorker calls the done method from the EDT. You
should not call this method directly because the SwingWorker
superclass will do it for you. The done method
retrieves the Icon result and puts it on the UI label.
In this example, the lblImage reference, a
JLabel component, was passed into the class
constructor.
@Override
protected void done() {
...
icon = get();
...
lblImage.setIcon(icon);
...
}
|
The progress property has int values from 0 through
100. As you process information from within the worker instance,
you can call the setProgress method to update this
property. As ImageRetriever downloads images using the
ImageIO API, it calls the setProgress
method to update its progress property. The following code shows
how this class updates its progress as it downloads an image using
an IIOReadProgressListener instance to track the work
of an ImageReader object:
reader.addIIOReadProgressListener(new IIOReadProgressListener() {
...
public void imageProgress(ImageReader source, float percentageDone) {
setProgress((int) percentageDone);
}
public void imageComplete(ImageReader source) {
setProgress(100);
}
});
|
When the worker's properties change, it notifies listener
objects. In the preceding example code, the
ImageRetriever class calls its setProgress
method as it receives update information from the
ImageIO API. It uses that information to set its own
progress property. As a result, property change listeners can know
how much of the image has been downloaded.
Figure 7 shows the ImageRetriever results in the
MainFrame UI. The progress bar shows that
the task is complete.
Figure 7. A SwingWorker thread updates both the progress bar and
the label image.
|
The ImageRetriever subclass demonstrates a simple
implementation of the SwingWorker class. In simple
implementations, your only real obligation is to override the
doInBackground method. However, because the worker's
final results are available only when that method finishes, you
should consider overriding the done method as well.
Retrieve the worker's results from within the done
method, which SwingWorker will invoke after the
doInBackground method completes. Retrieve the results
using the get method. By providing your UI components
in the constructor, you ensure that the worker thread will be able
to update those components directly from the done
method. The preceding example shows how to set a label's icon to the
retrieved image from the Flickr web site. Consult the complete
ImageRetriever implementation for details on how to
retrieve images from the Flickr web site.
Using a Simple ImageRetriever
Now that you've created a simple SwingWorker implementation such as
the ImageRetriever subclass, how do you use it? First, you
instantiate it, and then you call its execute method.
The application demo's MainFrame class uses an
ImageRetriever worker thread whenever the user selects a thumbnail from
the JList component. When the user clicks on a thumbnail, the
list selection changes, which generates a list selection event. The
listImagesValueChanged method is the event handler.
The listImagesValueChanged method retrieves the
selected list item and creates a Flickr service URL string that
corresponds to a larger image of the thumbnail. This event handler
then calls the retrieveImage method. The
retrieveImage method contains the important code for
creating and using the ImageRetriever worker thread.
private void listImagesValueChanged(ListSelectionEvent evt) {
...
ImageInfo info = (ImageInfo) listImages.getSelectedValue();
String id = info.getId();
String server = info.getServer();
String secret = info.getSecret();
// No need to search an invalid thumbnail image
if (id == null || server == null || secret == null) {
return;
}
String strImageUrl = String.format(IMAGE_URL_FORMAT,
server, id, secret);
retrieveImage(strImageUrl);
...
}
private void retrieveImage(String imageUrl) {
// SwingWorker objects can't be reused, so
// create a new one as needed.
ImageRetriever imgRetriever =
new ImageRetriever(lblImage, imageUrl);
progressSelectedImage.setValue(0);
// Listen for changes in the "progress" property.
// You can reuse the listener even though the worker thread
// will be a new SwingWorker.
imgRetriever.addPropertyChangeListener(listenerSelectedImage);
progressSelectedImage.setIndeterminate(true);
// Tell the worker thread to begin with this asynchronous method.
imgRetriever.execute();
// This event thread continues immediately here without blocking.
}
|
Notice that the retrieveImage method creates a new
ImageRetriever each time it downloads a new image. SwingWorker
instances are not reusable. You must create a new instance every
time you want to perform a task.
The correct way to use a SwingWorker is to instantiate it, add
any property change listeners that might need status event
notification from the thread, then execute the thread. The
execute method is asynchronous -- it returns
immediately to the caller. EDT execution continues immediately after
the execute method call, and the EDT will not be
disrupted by the long interaction with the web service. Again,
Figure 6 shows the interaction of these two threads.
The retrieveImage method shown earlier creates an
ImageRetriever and provides a JLabel reference that the
worker thread will use to display an image. The
retrieveImage method and its EDT no longer need access
to the worker thread. This code creates the thread, executes it, and
immediately resumes its processing of UI events as needed.
Notice that the retrieveImage method adds a property
change listener to the ImageRetriever instance before
executing the thread. The MainFrame class contains a
progress bar that tracks the status of the image download. The listener will receive
notification of all SwingWorker thread events. One of those events is a
progress event.
The following listener class responds to progress
events by updating a progress bar. The application has two progress
bars, one that tracks the search for and retrieval of the thumbnail
image and another that tracks the download of a larger image. The
application uses the same ProgressListener class -- but
different instances -- to track those tasks. Consequently, the
listener constructor requires that you provide the specific
component to update. Following is the ProgressListener
code:
/**
* ProgressListener listens to "progress" property
* changes in the SwingWorkers that search and load
* images.
*/
class ProgressListener implements PropertyChangeListener {
// Prevent creation without providing a progress bar.
private ProgressListener() {}
ProgressListener(JProgressBar progressBar) {
this.progressBar = progressBar;
this.progressBar.setValue(0);
}
public void propertyChange(PropertyChangeEvent evt) {
String strPropertyName = evt.getPropertyName();
if ("progress".equals(strPropertyName)) {
progressBar.setIndeterminate(false);
int progress = (Integer)evt.getNewValue();
progressBar.setValue(progress);
}
}
private JProgressBar progressBar;
}
|
The ImageRetriever class and its use are simple. Given an image
URL, it downloads the image. It also
provides progress information to a listener that updates a progress
bar in the MainFrame class. You don't have to know much
more about either creating or using a simple worker thread. However,
with just a little more effort, you can extract more work from a
SwingWorker subclass. The next sections show how to implement and
use a more complex worker subclass.
Implementing a More Complete ImageSearcher
A SwingWorker subclass can generate both final and intermediate
results. Remember, the thread produces a final result when the
doInBackground method finishes. However, a worker
thread can produce and publish intermediate chunks of data too. For
example, as the ImageSearcher subclass retrieves
a list of thumbnail images from a Flickr web service, it could display the
individual thumbnail images as they become available. There's no reason
to wait for the entire set of matching images to download before placing
intermediate results into a viewable list.
When implementing a SwingWorker subclass, you specify both the
final and intermediate result types in the class declaration. The
ImageSearcher subclass searches and downloads thumbnail
images that match a search term. To show that the class will produce
a complete list of matching images when it finishes, the class
uses the List<ImageInfo> type in its class type parameters.
To show that it will also publish each individual, intermediate matching image
from the list, it also uses the ImageInfo type in the class type
parameters. The ImageSearcher class begins like this:
public class ImageSearcher
extends SwingWorker<List<ImageInfo>, ImageInfo> {
public ImageSearcher(DefaultListModel model, String key,
String search, int page) {
this.model = model;
this.key = key;
this.search = search;
this.page = page;
}
...
}
|
From this small portion of the entire implementation, you know
several things. The class type parameters say
that the ImageSearcher's doInBackground and
get methods will return a list of
ImageInfo objects when the thread finishes. Also, the
class will publish individual ImageInfo objects as it
processes them, making them immediately viewable as they become
available. Because the class constructor uses a list model, you know
that the worker thread will probably update the model directly. As
you will see later, it does. Also, this worker thread needs a Flickr
API key -- provided by Flickr -- and the
search term. Because the web service provides results
in numbered pages, you can use the page argument to
determine what set of matching thumbnails to retrieve. For
simplicity, this demo always asks for the first page of matching
results.
Because the doInBackground method is the main focus of
any worker thread, look at ImageSearcher's implementation first:
@Override
protected List<ImageInfo> doInBackground() {
...
Object strResults = null;
InputStream is = null;
URL url = null;
List<ImageInfo> infoList = null;
try {
url = new URL(searchURL);
is = url.openStream();
infoList = parseImageInfo(is);
retrieveAndProcessThumbnails(infoList);
} catch(MalformedURLException mfe) {
...
}
return infoList;
}
|
This code opens a stream to the web service, providing a search
URL. The parseImageInfo method generates an information
list about matching images. It parses an XML file from the web
service. The retrieveAndProcessThumbnails method uses
the parsed list to download the thumbnail images. The final result
is a complete list of ImageInfo objects that contain
thumbnail data. The infoList object is the same type as
specified earlier in the class and method declarations. It has the
List<ImageInfo> type.
This class is similar to ImageRetriever because it updates a
progress bar and provides image data. This article will not describe
the doInBackground, done,
get, and setProgress methods further
because they are essentially the same as those in the other class.
However, the ImageSearcher class does not only retrieve a single
image; it downloads up to 100 matching thumbnail images. This
represents an opportunity to show off other SwingWorker features:
the publish and process methods.
You can use the publish method to deliver
intermediate results of your processing. As the ImageSearcher thread
downloads thumbnail images, it updates an image information list,
and it also publishes each chunk of image information so that the UI
can display those images as soon as they are available. Your
SwingWorker subclass should implement the process
method to process the intermediate results if it also publishes
them. The worker superclass will invoke the process
method on the EDT, so the application can safely update UI components from that
method.
The following code shows how the ImageSearcher class uses the
publish and process methods:
private void retrieveAndProcessThumbnails(List<ImageInfo> infoList) {
for (int x=0; x <infoList.size() && !isCancelled(); ++x) {
// http://static.flickr.com/{server-id}/{id}_{secret}_[mstb].jpg
ImageInfo info = infoList.get(x);
String strImageUrl = String.format("%s/%s/%s_%s_s.jpg",
IMAGE_URL, info.getServer(), info.getId(), info.getSecret());
Icon thumbNail = retrieveThumbNail(strImageUrl);
info.setThumbnail(thumbNail);
publish(info);
setProgress(100 * (x+1)/infoList.size());
}
}
/**
* Process is called as a result of this worker thread's calling the
* publish method. This method runs on the event dispatch thread.
*
* As image thumbnails are retrieved, the worker adds them to the
* list model.
*
*/
@Override
protected void process(List<ImageInfo> infoList) {
for(ImageInfo info: infoList) {
if (isCancelled()) {
break;
}
model.addElement(info);
}
}
|
To publish data intermittently instead of just at the thread's
completion, you should call the publish method. Provide
whatever data you want to publish as an argument. Of course, you
have to specify the intermediate data type in the class declaration
as described earlier. Again, this example uses the
ImageInfo type. The preceding
retrieveAndProcessThumbnails method code shows how to
publish individual ImageInfo objects as the thread
downloads thumbnail images.
When you call the publish method from the worker
thread, the SwingWorker class schedules a call to the
process method. Interestingly, the process
method will execute from the EDT. That means that you can interact
with Swing components and their models. The process
method adds ImageInfo objects into the thumbnail list
model, which means that the images will immediately appear in the
list as well.
Notice the process method's parameter. Instead of
using a single ImageInfo object, it expects and uses a
list of those objects. The reason is that the publish
method can batch calls to the process method. That
means that each publish invocation does not always
generate a corresponding process invocation. If
possible, the publish method will collect the objects
and will call the process method with a list of its
batched objects. Your implementation of the process method should
anticipate a list of objects, as does this one:
@Override
protected void process(List<ImageInfo> infoList) {
for(ImageInfo info: infoList) {
...
model.addElement(info);
}
}
|
If you want to allow application users to cancel a worker thread,
your code should check for cancellation requests periodically in its
SwingWorker subclass. Check for cancellation requests
using the isCancelled method. The
ImageSearcher subclass has several
isCancelled calls sprinkled throughout the code. Make
this call from within loops, iterators, and other checkpoints to
make sure that your thread learns about cancellation requests as
soon as possible. Your thread can periodically check for the request
and stop its work. The ImageSearcher class, for
example, checks for cancellation requests at several points:
- Before retrieving each thumbnail image, within a subtask of
the
doInBackground method
- Before updating the GUI list model with intermediate results,
within the
process method
- Before updating the GUI list model with final results,
within the
done method
The doInBackground method calls the
retrieveAndProcessThumbnails method. This method loops
through a list of image data and retrieves those thumbnail
images. However, the user can initiate a different search from the
EDT when the worker thread is in this loop. So it makes sense to
check for a cancellation here:
private void retrieveAndProcessThumbnails(List<ImageInfo> infoList) {
for (int x=0; x<infoList.size(); ++x) {
// Check whether this thread has been cancelled.
// Stop all thumbnail retrieval.
if (isCancelled()) {
break;
}
...
}
|
As this class processes the thumbnails, it publishes them. The
result is that the process method runs on the EDT. If
the user makes a cancellation request or initiates a new search
after the check in retrieveAndProcessThumbnails but
before the model update, the thumbnail will still appear in the
visual list. Prevent that by checking from within the
process method too:
protected void process(List<ImageInfo> infoList) {
for (ImageInfo info: infoList) {
if (isCancelled()) {
break;
}
model.addElement(info);
}
}
|
Finally, once the worker thread finishes, it has another chance
to update the model or the GUI in some way. Again, you should
probably check for a cancellation. The done method
provides the best opportunity for the check, and you can avoid
updating the GUI if the user has canceled or started a new search.
@Override
protected void done() {
...
if (isCancelled()) {
return;
}
...
// Update the model.
}
|
The ImageSearcher class is a more complete example of
SwingWorker's abilities because it does a little more than the
simpler ImageRetriever class. The ImageSearcher class publishes
intermediate data to the GUI, and it handles cancellation requests.
Both classes perform tasks in the background and track progress by
notifying event listeners.
Using the ImageSearcher Class
The demo application provides a search field. When a user enters
an image search term, the MainFrame class responds by
creating an ImageSearcher instance. Entering a search
term generates key events. The search field's key event handler
invokes the searchImages method, which instantiates and
executes an ImageSearcher thread:
private void searchImages(String strSearchText, int page) {
if (searcher != null && !searcher.isDone()) {
// Cancel current search to begin a new one.
// You want only one image search at a time.
searcher.cancel(true);
searcher = null;
}
...
// Provide the list model so that the ImageSearcher can publish
// images to the list immediately as they are available.
searcher = new ImageSearcher(listModel, API_KEY, strEncodedText, page);
searcher.addPropertyChangeListener(listenerMatchedImages);
progressMatchedImages.setIndeterminate(true);
// Start the search!
searcher.execute();
// This event thread continues immediately here without blocking.
}
|
Notice that the code provides the listModel
argument to the ImageSearcher constructor. Providing the model
allows the worker to directly update
the list contents. You can also add a property listener to the worker. This
code adds a property change listener to the worker thread. The
listener updates a progress bar. You could also add a listener to
respond to worker state changes. More specifically, you could
listen for the DONE state and then retrieve worker
results using the get method described earlier.
Once you execute the worker thread, it will search and retrieve
thumbnail images. This implementation has provided the list model to
the worker, so it will update the list directly. Also, the
ImageSearcher class provides intermediate data chunks,
so it updates the JList component as it downloads the
images. The immediate visual feedback improves application
performance.
As Figure 8 shows, the search results are small images that
populate a JList component.
Figure 8. Thumbnails are intermediate data produced by a worker
thread.
|
You can cancel a SwingWorker thread by calling its
cancel method. Demo users can cancel the current
image search by simply typing and entering another search term while
the current search is in progress. The search text field's event
handler checks whether existing threads are running and attempts to
cancel the thread:
private void searchImages(String strSearchText, int page) {
if (searcher != null && !searcher.isDone()) {
// Cancel current search to begin a new one.
// You want only one image search at a time.
searcher.cancel(true);
searcher = null;
}
...
}
|
After calling the cancel method, this code creates a
new worker instance. Every new search needs its own worker instance.
Summary
All GUI events and interactions run on the EDT. Running lengthy or I/O bound
processes on the EDT can cause your GUI
to become slow and unresponsive. Move those tasks to worker threads
by using the SwingWorker class available in the Java SE 6 platform.
Using SwingWorker, you can perform the same tasks without
delaying the EDT, which will improve your application's performance.
Moreover, the worker thread can interact with your UI because it has
callback methods that run on the EDT, allowing the worker to update
the GUI as it runs and when it finishes.
The Image Search demo program provides concrete examples of how
to implement and use the SwingWorker class. This demo uses worker
threads to search and download images from the Flickr image web
site. The Flickr web services require an API key to use, so you
should apply for and use your own API key if you plan to create your
own application that uses the services.
For More Information
|