Managing Long-Running Tasks in a Swing App

Getting a Swing application to work can often be its own challenge. Getting it to work "just right" all the more taxes one's experience and patience. One of the big challenges often revolves around the concept of a "long running task"—something, such as a big database query or a slow network connection, which takes longer than a second to execute. After all, you want the task to effectively run in parallel with the thread that handles the UI display to keep the application from seeming like it has gone into limbo (and looking amateurish when it stops repainting). And when the task finishes, determining whether it succeeded or failed, and knowing what information to show the user, can be its own headache.

In a previous article (MVC in a Java/Swing Application, May 2007), I introduced the concept of an Application Controller to separate the display logic from the decision making logic of a Swing-based application. I will expand on that topic a little further to show how to manage a long-running task. In fact, the main "JFrame" and "AppController" classes used there will be recycled and repurposed here. Therefore, you may want to review the previous article first. If you insist on skipping that article, at least be aware that the AppController class is a singleton, accessed via a static getInstance() method.

This article expects an intermediate level of experience, and the code snippets will include the use of Threads, generics, and enums.

Design Objectives

The typical use case is that the user presses a button and triggers a task that might take too long to complete to just execute directly in the event dispatch thread. Leaving such a task in the event dispatch thread means the entire UI freezes until the task is completed. It is completely unresponsive. (Users "dislike" unresponsive apps.) Thus, the task will be spun off in a Thread so the event dispatch thread can keep the visible UI bits painting on the screen and reacting to mouse and keyboard events. The user will need some visual cue that the task is currently in operation, and the command button that started the task probably should be disabled for safety.

When the task finishes, the UI will need to be updated. But, as experience teaches: You cannot always, cleanly, fiddle with the state of the UI directly from an external Thread and get away with it. The safe avenue is to schedule the update through the SwingUtilities class, but then that forces another Runnable instance to achieve this. And finally, those visual hints need to be turned off (and the command button turned back on) so the user knows the task has completed its work and that they can resume theirs.

But, that doesn't cover any failure scenarios: If an exception is thrown or some other recoverable error condition exists, a dialog should probably be shown with an appropriate message. Finally, out of politeness, you may want to provide a way to cancel the operation prematurely, by giving the user some form of an "abort" button.

It is a lot to coordinate, and if your Swing app hasn't achieved the right amount of MVC, any attempt at solving this quickly approaches spaghetti code. Fortunately, getting a working solution—with a solid strategy and a central application controller object such as I have discussed previously—is not that big of an ordeal.

Let me restate the design objectives in the following list of requirements:

An application controller is the point of contact and coordination to maintain a sense of MVC separation.

The task executes in response to a either button press or a programmatic invocation.

The task runs in a new thread, separate from the event dispatch thread. The user maintains a sense of being in control of the app.

So long as the task is executing, a visual hint of action is given to the user, and the button is disabled.

A cancel button is provided to abort the task early.

The task might finish successfully, be interrupted, or have an unexpected error. The user will be informed appropriately of any outcome.

To keep things even more interesting, the application will have a sense of the expected amount of time to complete the task, and will display a message to the user if the task seems to be taking an inordinate amount of time.

Related Articles

You will pick these items off as you go, but getting to the end result will need a couple of helper classes, so those will be covered first.

Task Details

TaskInfo

Connected to the act of a task running is the fact that there are multiple outcomes you want to keep track of: did it finish? was there a problem? what was the output? (and so forth). The task might require input data before starting, and likely has some data object of its own to represent its final state (and you will want to access this data object at a later point). If an exception is thrown, you likewise want to be able to access this exception at a later point.

The TaskInfo class I am showing here provides a completely generic way of keeping track of these details. This class does NOT include a Runnable implementation (that comes later) but instead provides you what you need to keep track of anything that goes into or comes out of a Runnable implementation.