Improve Application Performance With SwingWorker in Java SE 6
2009-12-25 01:48
543 查看
By John O'Conner, January 2007 |
![]() |
Contents
Introducing the Demo Application
Reviewing Swing Thread Basics
Starting off on the Right Thread
Limiting the EDT to GUI Work
Forging Ahead With SwingWorker Fundamentals
Implementing a Simple
ImageRetriever
Using a Simple
ImageRetriever
Implementing a More Complete
ImageSearcher
Using the
ImageSearcherClass
Summary
For More Information
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.SwingWorkerclass.
This article describes how to avoid slow, sluggish, or unresponsive UIs by using the
SwingWorkerclass to create and manage worker threads in a demo application called Image Search. This application demonstrates how to correctly use the
SwingWorkerapplication 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
SwingWorkerclass 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
JListcomponent. Matching images populate the list as they arrive from the Flickr site. Using a
SwingWorkerinstance, 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
SwingWorkerfor 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
SwingWorkersubclasses:
ImageSearcherand
ImageRetriever. The
ImageSearcherclass is responsible for searching and retrieving thumbnail images for the UI's list. The
ImageRetrieverclass 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
SwingWorkerclass. 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
mainmethod 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
SwingWorkerclass helps you manage the interaction between your worker threads and the Swing EDT. Although
SwingWorkerdoesn'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
mainmethod. 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
MainFrameclass. 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
SwingWorkerbut useful nevertheless, the
javax.swing.SwingUtilitiesclass contains a collection of static methods that help you interact with UI components. It includes the
invokeLatermethod that puts a
Runnabletask on the EDT. The
Runnableinterface defines a task that can be executed as a thread
Use the
invokeLatermethod 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
SwingUtilitiesversion is just a thin wrapper method that calls the
EventQueue.invokeLatermethod. Because the Swing framework itself uses
SwingUtilitiesfrequently, your use of
SwingUtilitiesdoes not pull any extra class code into your application.
Another way to put tasks on the EDT is to use the
SwingUtilities.invokeAndWaitmethod. Unlike the
invokeLatermethod, the
invokeAndWaitmethod is synchronous. It will execute the
Runnabletask on the EDT, but it will not return to the caller until the task has finished.
Both
invokeLaterand
invokeAndWaitwill execute their
Runnabletask after all other tasks on the event dispatch queue have been processed. In other words, these methods put the
Runnabletask at the end of the existing queue. Although you can use
invokeLaterfrom either your application or EDT, you should never use the
invokeAndWaitmethod from that thread. Using
invokeAndWaitfrom 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.SwingWorkerclass 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
SwingWorkerfunctionality. The
SwingWorkerdocumentation describes the class this way:
public abstract class SwingWorker<T,V> extends Object implements RunnableFuture |
The
SwingWorkerclass is abstract, so you must subclass it to perform the specific task you need. Notice that the class has type parameters
Tand
V. The
Ttype indicates that an implementation's
doInBackgroundand
getmethods will return values of
Ttype. The
Vtype indicates that an implementation's
publishand
processmethods 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.RunnableFutureinterface. The
RunnableFutureinterface is really nothing more than a wrapper for two separate interfaces:
Runnableand
Future.
Because it is
Runnable, a
SwingWorkerimplementation has a
runmethod. Runnable objects execute as part of a thread. A
Threadobject will invoke the
runmethod when it starts.
Because it is a
Futuretype, a
SwingWorkerwill provide its execution results in a value of type
Tand will provide a way to interact with the thread. The
SwingWorkerclass implements the following interface methods:
boolean cancel(boolean mayInterruptIfRunning)
T get()
T get(long timeout, TimeUnit unit)
boolean isCancelled()
boolean isDone()
The
SwingWorkerclass implements all of these interface methods for you. In fact, the only method you really must override is the following abstract
SwingWorkermethod:
protected T doInBackground() throws Exception |
The
doInBackgroundmethod 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
executemethod to schedule execution.
Use the worker's
getmethod to retrieve the results of the
doInBackgroundmethod. Although you can call the
getmethod 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
isDonemethod to check whether the
doInBackgroundmethod 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
donemethod:
protected void done() |
SwingWorkercalls this method after the
doInBackgroundmethod finishes. Override the
donemethod 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
getmethod because you know that the thread's work is finished when this method executes.
SwingWorkerinvokes the
donemethod 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
Vtype. Override the
processmethod to work with intermediate results. You will find more detail about these methods later in this article.
A
SwingWorkerinstance can notify listeners when its properties change. A
SwingWorkerinstance has two important properties: state and progress.
A worker thread has several states, represented by the following
SwingWorker.StateValueenumeration values:
PENDING
STARTED
DONE
A worker thread is in the
PENDINGstate immediately after its creation. When the
doInBackgroundmethod begins, the worker thread enters the
STARTEDstate. A worker thread is in the
DONEstate after its
doInBackgroundmethod finishes. The
SwingWorkersuperclass 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
progressproperty. 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
ImageRetrieverinstance and executes it. The
ImageRetrieverclass downloads a selected image from the thumbnail list and displays it below the list.
When implementing a
SwingWorkersubclass, you must specify the return value type of the
doInBackgroundand
getmethods. These methods return an
Iconobject in the
ImageRetrieverimplementation. Because
ImageRetrieverdoes not produce any intermediate results, it uses the special
Voidtype to indicate that fact. The following code shows most of the
ImageRetrieverimplementation:
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
ImageRetrieverclass will download an image and place it on a large label, providing the label and image URL in the constructor is convenient.
ImageRetrieverneeds the URL to retrieve an image. Provide the label so that the
ImageRetrieverinstance 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
ImageRetrieverinstances.
Notice how this class specifies the
Iconreturn type for the
doInBackgroundand
getmethods. The class specifies the
Voidtype for intermediate results because it does not produce any.
public class ImageRetriever extends SwingWorker<Icon, Void> |
In this implementation, the
doInBackgroundmethod must conform to the class contract by returning an
Iconobject. By indicating the
Icontype in the class definition, you are telling the compiler that both the
doInBackgroundand
getmethods will return an
Iconobject. You cannot override the
getmethod, because its default implementation is declared with the
finalkeyword.
The
doInBackgroundmethod retrieves an image from the URL provided in the class constructor, and it produces an
Iconresult:
@Override protected Icon doInBackground() throws Exception { Icon icon = retrieveImage(strImageUrl); return icon; |
When the
doInBackgroundmethod completes,
SwingWorkercalls the
donemethod from the EDT. You should not call this method directly because the
SwingWorkersuperclass will do it for you. The
donemethod retrieves the
Iconresult and puts it on the UI label. In this example, the
lblImagereference, a
JLabelcomponent, was passed into the class constructor.
@Override protected void done() { ... icon = get(); ... lblImage.setIcon(icon); ... } |
The progress property has
intvalues from 0 through 100. As you process information from within the worker instance, you can call the
setProgressmethod to update this property. As
ImageRetrieverdownloads images using the
ImageIOAPI, it calls the
setProgressmethod to update its progress property. The following code shows how this class updates its progress as it downloads an image using an
IIOReadProgressListenerinstance to track the work of an
ImageReaderobject:
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
ImageRetrieverclass calls its
setProgressmethod as it receives update information from the
ImageIOAPI. 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
ImageRetrieverresults in the
MainFrameUI. The progress bar shows that the task is complete.
![]() Figure 7. A SwingWorker thread updates both the progress bar and the label image. |
The
ImageRetrieversubclass demonstrates a simple implementation of the
SwingWorkerclass. In simple implementations, your only real obligation is to override the
doInBackgroundmethod. However, because the worker's final results are available only when that method finishes, you should consider overriding the
donemethod as well.
Retrieve the worker's results from within the
donemethod, which
SwingWorkerwill invoke after the
doInBackgroundmethod completes. Retrieve the results using the
getmethod. By providing your UI components in the constructor, you ensure that the worker thread will be able to update those components directly from the
donemethod. The preceding example shows how to set a label's icon to the retrieved image from the Flickr web site. Consult the complete
ImageRetrieverimplementation for details on how to retrieve images from the Flickr web site.
Using a Simple ImageRetriever
![]() |
Now that you've created a simple
SwingWorkerimplementation such as the
ImageRetrieversubclass, how do you use it? First, you instantiate it, and then you call its
executemethod. The application demo's
MainFrameclass uses an
ImageRetrieverworker thread whenever the user selects a thumbnail from the
JListcomponent. When the user clicks on a thumbnail, the list selection changes, which generates a list selection event. The
listImagesValueChangedmethod is the event handler.
The
listImagesValueChangedmethod 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
retrieveImagemethod. The
retrieveImagemethod contains the important code for creating and using the
ImageRetrieverworker 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
retrieveImagemethod creates a new
ImageRetrievereach time it downloads a new image.
SwingWorkerinstances are not reusable. You must create a new instance every time you want to perform a task.
The correct way to use a
SwingWorkeris to instantiate it, add any property change listeners that might need status event notification from the thread, then execute the thread. The
executemethod is asynchronous -- it returns immediately to the caller. EDT execution continues immediately after the
executemethod 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
retrieveImagemethod shown earlier creates an
ImageRetrieverand provides a
JLabelreference that the worker thread will use to display an image. The
retrieveImagemethod 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
retrieveImagemethod adds a property change listener to the
ImageRetrieverinstance before executing the thread. The
MainFrameclass contains a progress bar that tracks the status of the image download. The listener will receive notification of all
SwingWorkerthread events. One of those events is a
progressevent.
The following listener class responds to
progressevents 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
ProgressListenerclass -- but different instances -- to track those tasks. Consequently, the listener constructor requires that you provide the specific component to update. Following is the
ProgressListenercode:
/** * 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
ImageRetrieverclass 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
MainFrameclass. 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
SwingWorkersubclass. The next sections show how to implement and use a more complex worker subclass.
Implementing a More Complete ImageSearcher
![]() |
A
SwingWorkersubclass can generate both final and intermediate results. Remember, the thread produces a final result when the
doInBackgroundmethod finishes. However, a worker thread can produce and publish intermediate chunks of data too. For example, as the
ImageSearchersubclass 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
SwingWorkersubclass, you specify both the final and intermediate result types in the class declaration. The
ImageSearchersubclass 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
ImageInfotype in the class type parameters. The
ImageSearcherclass 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
doInBackgroundand
getmethods will return a list of
ImageInfoobjects when the thread finishes. Also, the class will publish individual
ImageInfoobjects 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
searchterm. Because the web service provides results in numbered pages, you can use the
pageargument to determine what set of matching thumbnails to retrieve. For simplicity, this demo always asks for the first page of matching results.
Because the
doInBackgroundmethod 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
parseImageInfomethod generates an information list about matching images. It parses an XML file from the web service. The
retrieveAndProcessThumbnailsmethod uses the parsed list to download the thumbnail images. The final result is a complete list of
ImageInfoobjects that contain thumbnail data. The
infoListobject is the same type as specified earlier in the class and method declarations. It has the
List<ImageInfo>type.
This class is similar to
ImageRetrieverbecause it updates a progress bar and provides image data. This article will not describe the
doInBackground,
done,
get, and
setProgressmethods further because they are essentially the same as those in the other class. However, the
ImageSearcherclass does not only retrieve a single image; it downloads up to 100 matching thumbnail images. This represents an opportunity to show off other
SwingWorkerfeatures: the
publishand
processmethods.
You can use the
publishmethod to deliver intermediate results of your processing. As the
ImageSearcherthread 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
SwingWorkersubclass should implement the
processmethod to process the intermediate results if it also publishes them. The worker superclass will invoke the
processmethod on the EDT, so the application can safely update UI components from that method.
The following code shows how the
ImageSearcherclass uses the
publishand
processmethods:
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
publishmethod. 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
ImageInfotype. The preceding
retrieveAndProcessThumbnailsmethod code shows how to publish individual
ImageInfoobjects as the thread downloads thumbnail images.
When you call the
publishmethod from the worker thread, the
SwingWorkerclass schedules a call to the
processmethod. Interestingly, the
processmethod will execute from the EDT. That means that you can interact with Swing components and their models. The
processmethod adds
ImageInfoobjects into the thumbnail list model, which means that the images will immediately appear in the list as well.
Notice the
processmethod's parameter. Instead of using a single
ImageInfoobject, it expects and uses a list of those objects. The reason is that the
publishmethod can batch calls to the
processmethod. That means that each
publishinvocation does not always generate a corresponding
processinvocation. If possible, the
publishmethod will collect the objects and will call the
processmethod 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
SwingWorkersubclass. Check for cancellation requests using the
isCancelledmethod. The
ImageSearchersubclass has several
isCancelledcalls 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
ImageSearcherclass, for example, checks for cancellation requests at several points:
Before retrieving each thumbnail image, within a subtask of the
doInBackgroundmethod
Before updating the GUI list model with intermediate results, within the
processmethod
Before updating the GUI list model with final results, within the
donemethod
The
doInBackgroundmethod calls the
retrieveAndProcessThumbnailsmethod. 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
processmethod runs on the EDT. If the user makes a cancellation request or initiates a new search after the check in
retrieveAndProcessThumbnailsbut before the model update, the thumbnail will still appear in the visual list. Prevent that by checking from within the
processmethod 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
donemethod 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
ImageSearcherclass is a more complete example of SwingWorker's abilities because it does a little more than the simpler
ImageRetrieverclass. The
ImageSearcherclass 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
MainFrameclass responds by creating an
ImageSearcherinstance. Entering a search term generates key events. The search field's key event handler invokes the
searchImagesmethod, which instantiates and executes an
ImageSearcherthread:
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
listModelargument to the
ImageSearcherconstructor. 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
DONEstate and then retrieve worker results using the
getmethod 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
ImageSearcherclass provides intermediate data chunks, so it updates the
JListcomponent 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
JListcomponent.
![]() Figure 8. Thumbnails are intermediate data produced by a worker thread. |
You can cancel a
SwingWorkerthread by calling its
cancelmethod. 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
cancelmethod, 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
SwingWorkerclass 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
SwingWorkerclass. 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
![]() |
Download the Image Search demo source code.
Download Java SE 6.
Read the SwingWorker documentation.
Read about the
@Overrideannotation.
Read the
ImageIOdocumentation.
Create your own Flickr web service client using Flickr APIs and keys.
Subscribe to Core Java Technologies Tech Tips.
Read the Java Tutorials: Creating a GUI with JFC/Swing.
原文转自:http://java.sun.com/developer/technicalArticles/javase/swingworker/
相关文章推荐
- JDBC Performance Tips – 4 Tips to improve performance of Java application with database
- Exception in thread "AWT-EventQueue-0" java.lang.NoClassDefFoundError: org/jdesktop/swingworker/SwingWorker
- Multithreading in swing with SwingWorker
- GitHub - naver/pinpoint: Pinpoint is an open source APM (Application Performance Management) tool for large-scale distributed systems written in Java.
- Exception in thread "AWT-EventQueue-0" java.lang.NoClassDefFoundError: org/jdesktop/swingworker/SwingWorker
- HTML/CSS/JavaScript GUI in Java Swing Application
- The missing SwingApplication class in Java Swing Framework.
- The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved in either web.xml or the jar files deployed with this application的解决办法
- HTTP Status 500 - The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved in either web.xml or the jar files deployed with this application
- Monitor and diagnose performance in Java SE 6--转载
- DETECTED ERROR IN APPLICATION: JNI GetMethodID called with pending exception java.lang.NoSuchMethodE
- Bug-JNI: JNI DETECTED ERROR IN APPLICATION: JNI CallVoidMethodV called with pending exception 'java.
- Java SE Application Design With MVC
- exception http://java.sun.com/jsp/jstl/core cannot be resolved in either web.xml or the jar files deployed with this application
- HTTP Status 500 - The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved in either web.xml or the jar files deployed with this application
- maven使用jstl表达式和The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved in either web.xml or the jar files deployed with this application解决
- Java SE Application Design With MVC
- This absolute uri http://java.sun.com/jsp/jstl/core cannot be resolved in either web.xml or the jar files deployed with this application
- Java Unit Testing with JUnit in NetBeans
- Remote Debug with Java Application Server