April 24, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Managing Long-Running Tasks in a Swing App, Page 2

  • January 24, 2008
  • By Rob Lybarger
  • Send Email »
  • More Articles »

Note this implementation uses generics, so this code will require at least Java 1.5 to run. (If you need to run on 1.4 or older, remove the generics code bits and make the "taskData" instance variable of type Object, and be careful of the effects of casting the Object where used later.) The use of generics, as opposed to a vanilla Object type, will add type-safety later when you extract the "taskData" object. By using an approach such as this, the TaskInfo can be given any sort of object to keep track of its details. (Although you will see a StringBuffer used, below, to collect a messages, you could provide your own custom type.)

The idea for this class is that, for any given task, a TaskInfo is created and given to the task. As the task executes, it will update its TaskInfo appropriately, mainly by keeping the "state" and "taskData" variables updated, and by setting the value of the exception variable, should one arise.

When the task completes, you will see that control passes back through the app controller, where code responsible for updating the display will determine exactly what to display based on the final state of the TaskInfo object.

BufferFillerTask

Being a article demo, this class doesn't do anything too exciting: it fills a StringBuffer object with the value "Hello, World!". However, to simulate the effects of a "long running task" (and to give it enough time to be properly cancelled) it first sleeps for several seconds. Also, to simulate something going wrong every once in a while, it will randomly throw an Exception saying "Something awful happened." (And, of course, if the task is interrupted by the user, an "Operation cancelled" message is shown.)



Click here for a larger image.

Operation Succeeded


Click here for a larger image.

Operation Failed


Click here for a larger image.

Operation Cancelled

Keep in mind that, as the task goes about its normal functions, it will need to keep its TaskInfo object (passed in via the constructor) updated:

import java.util.Random;

public class BufferFillerTask implements Runnable {

   private TaskInfo<StringBuffer> info;

   public BufferFillerTask(TaskInfo<StringBuffer> info) {
      this.info = info;
   }
   
   public void run() {
      info.setState(TaskInfo.State.RUNNING);
      try {
         Thread.sleep(4000);

         if (new Random().nextInt(3)==0) {
            throw new Exception("Something awful happened!");
         }

         info.getTaskData().append("Hello, World!");
         info.setState(TaskInfo.State.FINISHED);
      }
      catch (InterruptedException e) {
         info.setState(TaskInfo.State.WAS_INTERRUPTED);
      }
      catch (Exception e) {
         info.setException(e);
         info.setState(TaskInfo.State.HAD_EXCEPTION);
    }
   }
}
MVC design note: The task does not deal with the display of its result. Rather, it deals only with obtaining the result.

The AppController

Following the same usage pattern as in the preceding article where I introduced the AppController concept, the UI panel or frame that instantiates the command button will also send a reference to the button to the AppController (so that the AppController can invoke its setEnabled method). It also will register an anonymous ActionListener to call a method in the AppController to get the task started up.

The new part here involves the details of starting the task and dealing with the completion of the task. I keep these two parts in separate methods for a couple of reasons: First, it allows the task completion method to be reused by multiple related tasks, and second, it allows the task completion method to be tested and debugged independently:

public void handleBeginTaskAction() {
   StringBuffer buffer = new StringBuffer();
   final TaskInfo<StringBuffer> info =
      new TaskInfo<StringBuffer>(buffer);
   BufferFillerTask task = new BufferFillerTask(info);

   Runnable doneHandler = new Runnable() {
      public void run() { handleTaskCompleted(info); }
   };

   taskButton.setEnabled(false);
   textComponent.setText("Starting task...");
   new TaskController(task, doneHandler).start();
}

void handleTaskCompleted(TaskInfo<StringBuffer> info) {
   taskButton.setEnabled(true);
   switch (info.getState()) {
   case FINISHED:
      StringBuffer buffer = info.getTaskData();
      textComponent.setText(buffer.toString());
      break;
   case WAS_INTERRUPTED:
      textComponent.setText("Operation cancelled.");
      break;
   case HAD_EXCEPTION:
      textComponent.setText(info.getException().getMessage());
      break;
   }
}




Page 2 of 4



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel