Use worker thread for long-running tasks

Swing uses a single threading model to draw components on screen. Every request for repaint is queued in the render queue, and this single thread executes the painting code for every component.

If you have a long-running task, such as sending a file over a network, calculating a complexe formula, etc. it is not a good idea to let this processing execute in the main Swing Event Dispatch Thread. This will make the UI very unresponsive, since the single thread will be busy doing something else than repainting the screen. In fact, this is the misunderstanding of how Swing works that often makes people say that Swing is slow.

There are various solutions to enhance the user experience by getting out of the main dispatcher thread. I’ll concentrate on two of them : SwingWorker and Foxtrot. Since their respective website explain quite clearly each of them, I will be short on descriptions. As of Java 5.0 you can also use java.util.concurrent.ExecutorService to execute long-running tasks on a non-swing thread.

SwingWorker : the asynchronous solution

SwingWorker has been developped by Sun but is NOT available with the standard Swing distribution. You have to download the special class from Sun’s website and include it in your project.

A sample usage of this class is as follows :

SwingWorker worker = new SwingWorker() {
    public Object construct() {
        double pi = ...;  // long-running calculation
        return new Double(pi);    
    }
 
    public void finished() {
         // this method executes in the Swing event dispatcher thread (EDT)
         // when the construct() method has completed   
         // get() is a method from SwingWorker that returns the result of construct()
         Double result = (Double) get();  
         // since we are in the EDT, we can update the GUI.
         myJTextField.setText(result.toString());
    }
};
 
worker.start();  //this will start the worker thread, asynchronously from the EDT.

For more information, see Sun’s website.

Foxtrot : the synchronous solution

Sometimes, the SwingWorker might not exactly suit your needs. You may need to wait for the long-running task to complete, but without the drawbacks of the Event Dispatcher.

Since the SwingWorker executes asynchronously, once the call to worker.start() is made, the code after continues execution. Though most of the time it is useful, this is not always the case. This is when Foxtrot comes in the picture. In contrast to SwingWorker, Foxtrot “re-routes” the event dispatcher queue while executing the long-running task in its own worker thread.

lblStatus.setText("Processing PI...");
 
try {
    Double result = (Double) Worker.post(new Task() {
        public Object run() throws Exception {
            double pi = ...;  // long calculation
            return new Double(pi);
        }
    }
    
    lblStatus.setText("Completed. PI is " + result.toString());
} catch(Exception e) {
    // if any exception is thrown in the long-running task, it can be processed here.
}

[Is this example syntactically correct? It looks like the opening parenthesis after post does not have a matching closing parenthesis]

java.util.concurrent.ExecutorService: Java V5.0's asynchronous solution

In Java V5.0’s java.util.concurrent Threads are abstracted to Tasks. When using Threads directly, you override the run() method to do something useful and then create the Thread object and call start() which initializes the thread and calls the run() method. This thread initialization is not cheep. Allen Holub (Taming Java Threads, pp209) reports it takes 15 times longer to start the thread then it does to create it. Also the number of threads can be a limited resource in some systems.

With this in mind you can see the advantage of separating the task to be run from the thread running it and using a pool of reusable threads to run the tasks. This functionality is provided by java.util.concurrent. Instead on concentrating on Threads and their run() methods, java.util.concurrent instead talks about tasks which implement Callable<V>. The Callable<V> interface defines just one method,

V call() throws Exception

which computes a result, or throws an exception if unable to do so. This is a noticeable improvement over the Thread’s run() method because call() can return a result and throw a checked exception. Building on the Callable interface, java.util.concurrent provides classes for the asynchronous execution of Callable tasks. The Future<V> interface represents the result of an asynchronous task. The following simple example creates a Callable task and submits it for execution and waits for the result

        // create a simple task which just returns a String result
        Callable<String> task = new Callable<String>() {
            public String call() throws Exception {
                return "Finished the task";
            }
        };
        
        // get some executor, in this case an executor with just one thread available
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        // submit the task to be executed and get a future object back
        Future<String> future = executorService.submit(task);
        // loop waiting until the task is finished
        while (!future.isDone()) {
            try {
                Thread.sleep(100); // wait a while
            } catch(InterruptedException iex) {  }
        }
        // ok task done lets see what happened
        try {
            // try and get the result and print it
            String result = future.get();
            System.out.println(result);
        } catch (Throwable t) {
            // handle error returns here
            t.printStackTrace();  // not a good way of handling errors !!!
        }
    }

One glaring omission from java.util.concurrent is task listeners. As illustrated here, without task listeners you have to either poll the future object to see when the task is done or call future.get() directly, which just waits for the result. FutureTalker is a simple extension package for java.util.concurrent which allows you to add listeners to your task. The listeners will be called when the task completes, throws an error or is cancelled.

See also

External links

 

Comments? Corrections? Contact us or Login to edit pages directly (registration is free and takes less than displaying a JLabel)
  best/use_worker_thread_for_long_operations.txt · Last modified: 2007/02/26 17:08 by 81.207.20.120 (maarten)
 
Recent changes | RSS changes | Table of contents | News Archive | Terms And Conditions | Register