Foxtrot

SourceForge.net Logo Java.net Logo

Overview

The Problem

Asynchronous Solution

Synchronous Solutions

Foxtrot

Foxtrot & Swing

Foxtrot's synchronous solution: ConcurrentWorker

Foxtrot's ConcurrentWorker is a synchronous solution like Worker.
Where Worker enqueues the Tasks or Jobs to be run in a single worker queue, so that they're executed one after the other, in ConcurrentWorker the Tasks or Jobs are run as soon as they are posted and each in its own worker thread.

While at first ConcurrentWorker seems more powerful than Worker, it has a peculiar behavior that is less intuitive than Worker, and may lead to surprises when used in the wrong context.

The ideal context for the correct usage of ConcurrentWorker is when a task posted after a first task needs to be executed concurrently with the first task and there must be a synchronous behavior (that is, the first call to ConcurrentWorker.post() must complete only after the second call to ConcurrentWorker.post()).

A typical example is when an application executes a time-consuming task that can be canceled, but the action of cancelling is also a time-consuming operation.

If Worker is used in this case, the two tasks will be both enqueued in the single worker queue of Worker, and the second task (that should cancel the first one) gets the chance of being executed only after the first task completes, making it useless.

ConcurrentWorker instead executes the tasks as soon as they are posted, so in the above case the second task is executed concurrently with the first task in order to get the chance of cancelling the first task while it is still being executed.
However, the application requires that the first call to ConcurrentWorker.post() returns only after the second call to ConcurrentWorker.post() completes cancelling the first task, so that any code after the first ConcurrentWorker.post() call can discern whether the task completed successfully or was canceled.

Remembering that ConcurrentWorker is a synchronous solution is the key to avoid to use it in the wrong contexts.
The following example shows that using ConcurrentWorker in a strictly synchronous context does not lead to any benefit with respect to Worker, and it's probably slower, because calls to ConcurrentWorker.post() block until the task is completed. Therefore, posting two jobs consecutively in the code results in the jobs being executed one after the other, because the first call to blocks until the first task is completed.

Legend
Main Thread
Event Dispatch Thread
Foxtrot Worker Thread

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

   public ConcurrentWorkerWrongExample1()
   {
      super("ConcurrentWorker Wrong Example 1");

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

            
            // Blocking call
            ConcurrentWorker.post(new Job()
            {
               public Object run()
               {
                  sleep(10000);
                  return null;
               }
            });

            
            // Blocking call
            ConcurrentWorker.post(new Job()
            {
               public Object run()
               {
                  sleep(5000);
                  return null;
               }
            });
            

            button.setText("Slept !");
            
         }
      });

      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);
   }

   public void sleep(long ms)
   {
      try
      {
         Thread.sleep(ms);
      }
      catch (InterruptedException x)
      {
         Thread.currentThread().interrupt();
      }
   }
}

Another wrong example is to use ConcurrentWorker in an asynchronous context.
For example, suppose to have an application with a javax.swing.JTabbedPane, and suppose that each tab takes a while to load the data it presents to the user. A user may click on the first tab, then click on the second tab before the first finishes loading, then on the third before the second finishes loading and so on. The loading of each tab is usually an asynchronous operation, since we want the tabs to load concurrently, but we do not want that the first tab waits until the last tab is loaded. Each tab loading is independent from the others. The correct solution in this cases is to use AsyncWorker.

Using ConcurrentWorker in the code that loads the tabs results in the tabs being displayed one after the other, from the last to the first, which is not what is normally expected.