Foxtrot

SourceForge.net Logo Java.net Logo

Overview

The Problem

Asynchronous Solution

Synchronous Solutions

Foxtrot

Foxtrot & Swing

Asynchronous Solutions

Solutions have been proposed for the GUI freeze problem; asynchronous solutions rely on the combined usage of a worker thread and the SwingUtilities.invokeLater() method. We will see in few lines why they're called asynchronous.

The main idea behind an asynchronous solution is to return quickly from the time-consuming listener, after having delegated the time-consuming task to a worker thread. The worker thread, usually, does two things:

  • Executes the time-consuming task
  • Posts an event to the Event Queue using SwingUtilities.invokeLater()

Take a look at the code below which uses Foxtrot's AsyncWorker, which is an asynchronous solution.

Let's concentrate on the button's listener (the actionPerformed() method): the first statement, as in the freeze example, changes the text of the button and thus posts a repaint event to the Event Queue.
The next statement posts an AsyncTask to Foxtrot's AsyncWorker. This operation is quick, non blocking, and returns immediately.
Posting an AsyncTask to Foxtrot's AsyncWorker causes the Foxtrot worker thread to start executing the code contained in the run() method of AsyncTask; the Event Dispatch Thread is free to continue its execution, and will execute the somethingElse() method.
Therefore, there is a potentially concurrent execution of the code in the AsyncTask and of the code in somethingElse(). When the run() method of AsyncTask ends, one of two callbacks is called and executed in the Event Dispatch Thread:

  • method success(Object result) in case the AsyncTask execution completed successfully, or
  • method failure(Throwable x) in case the AsyncTask execution throws an Exception or an Error

This is why these solutions are called asynchronous: the code in the event listener and the code of the AsyncTask are executed concurrently and noone waits for the other to complete.

This solution, while resolving the freeze problem, has several drawbacks:

  • Note the non-optimal exception handling. The exception handling is done inside the AsyncTask, not inside the listener, where it would be more intuitive.
  • Note the asymmetry: the first setText() is made outside the AsyncTask, the second inside of it.
  • What happens if the time-consuming code stays the same, but we want to execute 2 different success(Object result) methods depending on the place from where we want to execute the time-consuming task ?
  • If some code is written after AsyncWorker.post(), like the somethingElse() method, it will be always executed before the success(Object result) or failure(Throwable x) callbacks. Reading the source code we see, from top to bottom:
    • setText("Sleeping...")
    • Thread.sleep()
    • setText("Slept !");
    • somethingElse()
    but the real order of execution is:
    • setText("Sleeping...")
    • somethingElse() [before, concurrently or after Thread.sleep()]
    • Thread.sleep()
    • setText("Slept !");
    making debugging and code readability very difficult.
    This is why a golden rule of AsyncWorker is to never put code after AsyncWorker.post().
  • If the code inside success(Object result) requires a new time-consuming operation, a new nested AsyncWorker should be used, making the code complex and obscure, especially with respect to the sequence order of the operations executed.

Fortunately, Foxtrot's synchronous solutions solve these issues.

Legend
Main Thread
Event Dispatch Thread
Foxtrot Worker Thread

public class AsyncExample extends JFrame
{
   public static void main(String[] args)
   {
      AsyncExample example = new AsyncExample();
      example.setVisible(true);
   }

   public AsyncExample()
   {
      super("AsyncWorker Example");

      final JButton button = new JButton("Take a nap !");
      button.addActionListener(new ActionListener()
      {
         public void actionPerformed(ActionEvent e)
         {
            button.setText("Sleeping...");

            AsyncWorker.post(new AsyncTask()
            {
               public Object run() throws Exception
               {
                  Thread.sleep(10000);
                  return "Slept !";
               }

               public void success(Object result)
               {
                  String text = (String)result;
                  button.setText(text);
               }

               public void failure(Throwable x)
               {
                  // Do exception handling: argument x is the Throwable
                  // that is eventually thrown inside run()
               }
            });

            somethingElse();
         }
      });

      setDefaultCloseOperation(DISPOSE_ON_CLOSE);

      Container c = getContentPane();
      c.setLayout(new GridBagLayout());
      c.add(button);

      setSize(300,200);

      Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
      Dimension size = getSize();
      int x = (screen.width - size.width) >> 1;
      int y = (screen.height - size.height) >> 1;
      setLocation(x, y);
   }
}