EDT mouse
click
action event
action listener
update table model table
changed event
table listener
update table view
Figure 9.2. Control flow with separate model and view objects.
modifies the contents of a table, the action listener would update the model and call one of the fireXxx methods, which would in turn invoke the view’s table model listeners, which would update the view. Again, control never leaves the event thread. (The Swing data model fireXxx methods always call the model listeners directly rather than submitting a new event to the event queue, so the fireXxxmethods must be called only from the event thread.)
9.3 Long-running GUI tasks
If all tasks were short-running (and the application had no significant non-GUI portion), then the entire application could run within the event thread and you wouldn’t have to pay any attention to threads at all. However, sophisticated GUI applications may execute tasks that may take longer than the user is will- ing to wait, such as spell checking, background compilation, or fetching remote resources. These tasks must run in another thread so that the GUI remains re- sponsive while they run.
Swing makes it easy to have a task run in the event thread, but (prior to Java 6) doesn’t provide any mechanism for helping GUI tasks execute code in other threads. But we don’t need Swing to help us here: we can create our own Executor for processing long-running tasks. A cached thread pool is a good choice for long-running tasks; only rarely do GUI applications initiate a large number of long-running tasks, so there is little risk of the pool growing without bound.
We start with a simple task that does not support cancellation or progress indication and that does not update the GUI on completion, and then add those features one by one. Listing9.4shows an action listener, bound to a visual compo- nent, that submits a long-running task to anExecutor. Despite the two layers of inner classes, having a GUI task initiate a task in this manner is fairly straightfor- ward: the UI action listener is called in the event thread and submits aRunnable to execute in the thread pool.
This example gets the long-running task out of the event thread in a “fire and forget” manner, which is probably not very useful. There is usually some sort of visual feedback when a long-running task completes. But you cannot access presentation objects from the background thread, so on completion the task must submit another task to run in the event thread to update the user interface.
ExecutorService backgroundExec = Executors.newCachedThreadPool();
...
button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) {
backgroundExec.execute(new Runnable() { public void run() { doBigComputation(); } });
}});
Listing 9.4. Binding a long-running task to a visual component.
Listing9.5illustrates the obvious way to do this, which is starting to get com- plicated; we’re now up to three layers of inner classes. The action listener first dims the button and sets a label indicating that a computation is in progress, then submits a task to the background executor. When that task finishes, it queues another task to run in the event thread, which reenables the button and restores the label text.
button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) {
button.setEnabled(false);
label.setText("busy");
backgroundExec.execute(new Runnable() { public void run() {
try {
doBigComputation();
} finally {
GuiExecutor.instance().execute(new Runnable() { public void run() {
button.setEnabled(true);
label.setText("idle");
} });
} } });
} });
Listing 9.5. Long-running task with user feedback.
The task triggered when the button is pressed is composed of three sequen- tial subtasks whose execution alternates between the event thread and the back- ground thread. The first subtask updates the user interface to show that a long- running operation has begun and starts the second subtask in a background
9.3. Long-running GUI tasks 197 thread. Upon completion, the second subtask queues the third subtask to run again in the event thread, which updates the user interface to reflect that the operation has completed. This sort of “thread hopping” is typical of handling long-running tasks in GUI applications.
9.3.1 Cancellation
Any task that takes long enough to run in another thread probably also takes long enough that the user might want to cancel it. You could implement cancellation directly using thread interruption, but it is much easier to useFuture, which was designed to manage cancellable tasks.
When you callcancelon aFuturewithmayInterruptIfRunning set totrue, theFutureimplementation interrupts the thread that is executing the task if it is currently running. If your task is written to be responsive to interruption, it can return early if it is cancelled. Listing9.6illustrates a task that polls the thread’s interrupted status and returns early on interruption.
Future<?> runningTask = null; // thread-confined ...
startButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) {
if (runningTask == null) {
runningTask = backgroundExec.submit(new Runnable() { public void run() {
while (moreWork()) {
if (Thread.currentThread().isInterrupted()) { cleanUpPartialWork();
break;
}
doSomeWork();
} } });
};
}});
cancelButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) {
if (runningTask != null) runningTask.cancel(true);
}});
Listing 9.6. Cancelling a long-running task.
Because runningTask is confined to the event thread, no synchronization is required when setting or checking it, and the start button listener ensures that
only one background task is running at a time. However, it would be better to be notified when the task completes so that, for example, the cancel button could be disabled. We address this in the next section.
9.3.2 Progress and completion indication
Using aFutureto represent a long-running task greatly simplified implementing cancellation.FutureTaskalso has adonehook that similarly facilitates completion notification. After the backgroundCallablecompletes,doneis called. By having done trigger a completion task in the event thread, we can construct a Back- groundTask class providing an onCompletion hook that is called in the event thread, as shown in Listing9.7.
BackgroundTask also supports progress indication. Thecomputemethod can callsetProgress, indicating progress in numerical terms. This causes onProg- ressto be called from the event thread, which can update the user interface to indicate progress visually.
To implement aBackgroundTask you need only implementcompute, which is called in the background thread. You also have the option of overridingonCom- pletionandonProgress, which are invoked in the event thread.
Basing BackgroundTask on FutureTask also simplifies cancellation. Rather than having to poll the thread’s interrupted status,compute can callFuture.is- Cancelled. Listing 9.8 recasts the example from Listing 9.6 usingBackground- Task.
9.3.3 SwingWorker
We’ve built a simple framework usingFutureTaskandExecutorto execute long- running tasks in background threads without undermining the responsiveness of the GUI. These techniques can be applied to any single-threaded GUI framework, not just Swing. In Swing, many of the features developed here are provided by the SwingWorker class, including cancellation, completion notification, and progress indication. Various versions of SwingWorker have been published inThe Swing ConnectionandThe Java Tutorial, and an updated version is included in Java6.