BeforeAfterCallable and friends

The before/after pattern in two threads

Contents:


Overview

Interface BeforeAfterCallable<ValueT>

Type Parameters:
ValueT - the result type returned by call().

public interface BeforeAfterCallable<ValueT>

A compound action that has a setup, an action, and a cleanup, implemented in three methods, before(), call(), and after(), to be run in what is known as the before/after pattern, which goes like this:
  before();
  try { result = call(); }
  finally { after(); }

The BeforeAfterCallable interface is used with BeforeAfterCaller to run call() in another thread.

Usage goes like this:
  MyCallable c = new MyCallable(); // implements BeforeAfterCallable<V>
  BeforeAfterCaller<V> caller = new BeforeAfterCaller<V>(executor);
  BeforeAfterFutureTask<V> future = caller.submit(c);

Essentially, submit() does this:
  // in the submitting thread:
  c.before();
  // possibly in another thread, depending on executor:
  try {
    c.call(); // and set value or exception of future
  } finally {
    c.after();
  }

submit() makes these guarantees:

before() is required to trigger the calling of call() by calling the proceed() method on its allowCallToProceed argument. before() may do so either by calling proceed() directly or by calling a method that blocks and triggers the calling of proceed() in another thread. Typically the blocking method will block until after() does something to allow it to unblock (as in the second example below). submit() throws an Error if before() returns before proceed() has been called.

If call() is to be called in another thread, BeforeAfterCaller uses a CyclicBarrier to guarantee that if submit() returns successfully, then call() has been called or will be called.

When call() has been called and has succeeded in returning a value, it is nevertheless possible that a Throwable was thrown in before() after the call to proceed() or thrown in after() (neither of which can interfere with the returning of the value via the Future.get() method). There are methods in BeforeAfterFutureTask to find out whether such Throwables were thrown.

Sample Usage

The motivation for building the BeforeAfterCaller system was to show progress in a dialog window which may or may not be modal, decided by the user at runtime. A demo program provided with the source embodies the examples below.

Nonmodal dialog - If the Executor uses the same thread, then submit() returns when after() returns; however, if the Executor uses a different thread, then the submitter may optionally wait for after() to finish by calling future.get().

Modal dialog - The same code, with only minimal customization, can also handle the case where the dialog is modal. In this case, before() blocks, so the Executor must use a different thread for the worker thread.


SimpleDemo

Here is a simple example:

class SimpleDemo {

  public static void main(String[] args) {
    BeforeAfterCaller<String> b = new BeforeAfterCaller<String>();
    runDemo(b);
  }

  private static void runDemo(BeforeAfterCaller<String> b) {
    BeforeAfterCallableForDemo demoTask = new BeforeAfterCallableForDemo();
    try {
      BeforeAfterFutureTask<String> future = b.submit(demoTask);
      String result = /*(String)*/future.get();
      BeforeAfterCallableForDemo task = ((BeforeAfterCallableForDemo) future.getTask());
      int count = task.count;
      System.out.println("result is " + "\"" + result + "\" and count is " + count);
      checkForOtherErrors(future);
    } catch (Throwable th) {
      System.err.println("Failed with " + th);
    }
  }

  private static void checkForOtherErrors(BeforeAfterFutureTask<String> future) {
    final Throwable beforeThrowable = future.getBeforeThrowable();
    if (beforeThrowable != null) {
      System.out.println("Programming error in before(): " + beforeThrowable);
    }
    final Throwable  afterThrowable = future.getAfterThrowable();
    if (afterThrowable != null) {
      System.out.println("Problem in after(): " + afterThrowable);
    }
  }
}

//=============================================================================================

class BeforeAfterCallableForDemo implements BeforeAfterCallable<String> {

  static final String CORRECT_RESULT = "Hello, folks of World!";

  // Every time count is incremented, it must also be decremented.
  volatile int count;

  public void before(AllowCallToProceed allowCallToProceed) throws Exception {
    ++count;
    System.out.println("task.before()");
    allowCallToProceed.proceed();
  }

  public String/*Object*/ call() throws Exception {
    System.out.println("task.call()");
    return CORRECT_RESULT;
  }

  public void after(String/*Object*/ futureResult, Throwable callThrowable) {
    --count;
    System.out.println("task.after(): "
        + (futureResult    != null ? ("\"" + futureResult.toString() + "\"") : "[null]")
        + Throwables.throwableToStringWithCause(callThrowable,   "callThrowable: ", true)
    );
  }
}

Its output is:

1 Z% java SimpleDemo
task.before()
task.call()
task.after(): "Hello, folks of World!"
result is "Hello, folks of World!" and count is 0
2 Z%

DialogDemo

Here is a more complex example, showing a Swing JDialog with JProgressBar.

(Nowadays, the best way to do this would be to use the new SwingWorker class that will be part of Java 6.)

import java.util.concurrent.Executor;
import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.lang.reflect.InvocationTargetException;

/**
 * Put up a modal dialog,
 * then start a long-running task in a worker thread.
 */
class DialogDemo {

  public static void main(String[] args) throws Exception {
    OurBeforeAfterCallable demoTask = new OurBeforeAfterCallable();
    // These are the three cases documented in the BeforeAfterCallable javadoc.
    runIt(demoTask, false, false, true);
    runIt(demoTask, false, true, true);
    runIt(demoTask, true, true, false);
    // This one is extra.
    runIt(demoTask, true, true, true);
    System.exit(0);
  }

  private static BeforeAfterFutureTask<String> future;

  private static void runIt(final OurBeforeAfterCallable demoTask,
                            boolean isModal,
                            boolean useOtherThread,
                            boolean invokeAndWait
                           )
      throws InterruptedException, InvocationTargetException {
    demoTask.isModal = isModal;
    final Executor executor;
    if (isModal || useOtherThread) {
      executor = new ThreadPerTaskExecutor();
    } else {
      executor = new DirectExecutor();
    }
    final BeforeAfterCaller<String> b = new BeforeAfterCaller<String>(executor);
    if (isModal && invokeAndWait) {
      // Show that we can do this from the event thread
      // if we want to.
      SwingUtilities.invokeAndWait(new Runnable() { public void run() {
        doSubmit(b, demoTask);
      }});
    } else {
      doSubmit(b, demoTask);
    }
    try {
      System.out.println("future.get() returns " + future.get());
    } catch (Throwable th) {
      System.out.println("future.get() throws " + th);
    }
  }

  private static void doSubmit(final BeforeAfterCaller<String> b,
                               OurBeforeAfterCallable task) {
    task.startTimeMs = System.currentTimeMillis();
    try {
      future = b.submit(task);
    } catch (Throwable ex) {
      System.out.println("submit() throws " + ex);
    }
  }
}

//==============================================================================

class OurBeforeAfterCallable implements BeforeAfterCallable<String> {

  long startTimeMs;
  private JDialog dialog;
  private JProgressBar progressBar;
  boolean isModal;

  private static final int progressSteps = 60;
  private static final int intervalMs = 20;
  private static final int endMs = 400;

  public void before(final AllowCallToProceed allowCallToProceed)
      throws Exception
  {
    invokeAndWaitMaybe(new Runnable() { public void run() {
      dialog = new JDialog();
      progressBar = new JProgressBar(0, progressSteps);
      dialog.setModal(isModal);
      if (isModal)
      {
        // There are other ways we could call allowCallToProceed.run(),
        // but this way demonstrates a pretty-demanding use case.
        dialog.addWindowListener(new WindowAdapter() {
          public void windowOpened(WindowEvent e) {
            // Announce we're ready for proceed() to be called.
            if (!allowCallToProceed.proceed()) {
              e.getWindow().setVisible(false);
            }
          }
        });
      }
      dialog.getContentPane().setLayout(new BorderLayout());
      dialog.getContentPane().add(progressBar);
      dialog.pack();
      dialog.setVisible(true);
    }});
    if (!isModal) allowCallToProceed.proceed();
  }

  // We are guaranteed that the dialog is open before this is called.
  public String/*Object*/ call() throws Exception {
    for (int count = 0; count <= progressSteps; ++count) {
        final int count2 = count;
      SwingUtilities.invokeLater(new Runnable() { public void run() {
        progressBar.setValue(count2);
      }});
      printTime();
      Thread.sleep(intervalMs);
    }
    return "OK";
  }

  private boolean firstTime = true;

  private void printTime() {
    if (firstTime) {
      firstTime = false;
      long now = System.currentTimeMillis();
      System.out.println((now - startTimeMs)
                         + "ms from submit to first set of progressBar");
    }
  }

  // We are guaranteed that this is called after proceed() completes.
  public void after(String/*Object*/ futureResult,
                    Throwable callThrowable
                   ) {
    try {
      Thread.sleep(endMs);
    } catch (InterruptedException e) {
      // Tough.
    }
    SwingUtilities.invokeLater(new Runnable() { public void run() {
      dialog.setVisible(false);
    }});
  }

  static void invokeAndWaitMaybe(Runnable r)
      throws InvocationTargetException, InterruptedException {
    if (EventQueue.isDispatchThread()) {
      r.run();
    } else {
      SwingUtilities.invokeAndWait(r);
    }
  }
}

SwingWorkerDemo implemented using SwingWorker

import jozart.swingutils.SwingWorker;

import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.SwingConstants;
import javax.swing.Timer;
import java.awt.event.ActionListener;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;

public class SwingWorkerDemo extends javax.swing.JApplet {

  private static final int TIMEOUT = 3000; // 3 seconds
  private JLabel status;
  private JButton start;
  private Timer timer;
  private boolean timeOutThisTime = false;
  // SwingWorker stuff
  private Worker worker;

  public SwingWorkerDemo() {
    initWorker();
    initTimer();
    initGUI();
  }

  private void initWorker() {
    worker = new Worker();
  }

  private void initTimer() {
    timer = new Timer(TIMEOUT / 2, null);
    timer.addActionListener(new TimerActionListener());
    timer.setRepeats(false);
  }

  private void initGUI() {
    status = new JLabel("Ready");
    status.setHorizontalAlignment(SwingConstants.CENTER);
    start = new JButton("Start");
    start.addActionListener(new StartActionListener());
    getContentPane().add(status, java.awt.BorderLayout.CENTER);
    getContentPane().add(start, java.awt.BorderLayout.SOUTH);
  }

  private void doCancelled() {
    status.setText("Cancelled");
    start.setEnabled(false);
  }

  private void doTimedOut() {
    status.setText("Timed out");
    start.setEnabled(false);
  }

  //----------------------------------------------------------------------------
  
  private class StartActionListener implements ActionListener {
    public void actionPerformed(java.awt.event.ActionEvent evt) {
      if ("Start".equals(start.getText())) {
        worker.before();
        worker.start();
      } else if (worker.cancel(true)) {
        doCancelled();
      }
    }
  }

  private class TimerActionListener implements ActionListener {
    public void actionPerformed(java.awt.event.ActionEvent evt) {
      if (worker.cancel(true)) {
        doTimedOut();
      }
    }
  }

  // Everything runs in Event thread except as noted.
  private class Worker extends SwingWorker<String> {

    // Runs in other thread.
    protected String construct() throws InterruptedException {
      return call();
    }

    protected void finished() {
      try {
        afterCallSuccess(get());
      } catch (CancellationException ex) {
        afterInterrupted();
      } catch (ExecutionException ex) {
        afterCallFailure(ex);
      } catch (InterruptedException ex) {
        // event-dispatch thread won't be interrupted
        throw new IllegalStateException(ex + "");
      } finally {
        afterFinally();
      }
    }

    public void before()  {
      start.setText("Stop");
      final String statusText;
      if (timeOutThisTime) {
        timer.start();
        statusText = "Working...  (timeout enabled)";
      } else {
        statusText = "Working...";
      }
      status.setText(statusText);
      timeOutThisTime = !timeOutThisTime;
    }

    // Runs in other thread.
    public String call() throws InterruptedException {
      Thread.sleep(TIMEOUT);
      return "Success";
    }

    public void afterInterrupted() {
      // status text was set when cancelled
    }

    public void afterCallSuccess(String result) {
      status.setText(result);
    }

    public void afterCallFailure(Throwable th) {
      status.setText("Exception: " + th);
    }

    public void afterFinally() {
      timer.stop();
      start.setText("Start");
      start.setEnabled(true);
    }
  }
}

SwingWorkerDemo2 and SwingWorker2 implemented using BeforeAfterCallable

import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.SwingConstants;
import javax.swing.Timer;
import javax.swing.SwingUtilities;
import java.awt.event.ActionListener;
import java.util.concurrent.Executor;

public class SwingWorkerDemo2 extends javax.swing.JApplet {

  private static final int TIMEOUT = 3000; // 3 seconds
  private JLabel status;
  private JButton start;
  private Timer timer;
  private boolean timeOutThisTime = false;
  // SwingWorker2 stuff
  private Worker worker;
  private volatile BeforeAfterFutureTask<String> future;

  public SwingWorkerDemo2() {
    initWorker();
    initTimer();
    initGUI();
  }

  private void initWorker() {
    worker = new Worker();
  }

  private void initTimer() {
    timer = new Timer(TIMEOUT / 2, null);
    timer.addActionListener(new TimerActionListener());
    timer.setRepeats(false);
  }

  private void initGUI() {
    status = new JLabel("Ready");
    status.setHorizontalAlignment(SwingConstants.CENTER);
    start = new JButton("Start");
    start.addActionListener(new StartActionListener());
    getContentPane().add(status, java.awt.BorderLayout.CENTER);
    getContentPane().add(start,  java.awt.BorderLayout.SOUTH);
  }

  private void doCancelled() {
    status.setText("Cancelled");
    start.setEnabled(false);
  }

  private void doTimedOut() {
    status.setText("Timed out");
    start.setEnabled(false);
  }

  //----------------------------------------------------------------------------

  private class StartActionListener implements ActionListener {
    public void actionPerformed(java.awt.event.ActionEvent evt) {
      if ("Start".equals(start.getText())) {
        try {
          future = worker.start();
        } catch (Throwable th) {
          th.printStackTrace(); // Do something better.
        }
      } else if (future != null && future.cancel(true)) {
        doCancelled();
      }
    }
  }

  private class TimerActionListener implements ActionListener {
    public void actionPerformed(java.awt.event.ActionEvent evt) {
      if (future.cancel(true)) {
        doTimedOut();
      }
    }
  }

  // Everything runs in Event thread except as noted.
  class Worker extends SwingWorker2<String> {

    @Override
    public void before() {
      start.setText("Stop");
      final String statusText;
      if (timeOutThisTime) {
        timer.start();
        statusText = "Working...  (timeout enabled)";
      } else {
        statusText = "Working...";
      }
      status.setText(statusText);
      timeOutThisTime = !timeOutThisTime;
    }

    // Runs in other thread.
    @Override
    public String/*Object*/ call() throws InterruptedException {
      Thread.sleep(TIMEOUT);
      return "Success";
    }

    @Override
    public void afterInterrupted() {
      // status text was set when cancelled
    }

    @Override
    public void afterCallSuccess(String/*Object*/ result) {
      status.setText(/*(String)*/result);
    }

    @Override
    public void afterCallFailure(Throwable th) {
      status.setText("Exception: " + th);
    }

    @Override
    public void afterFinally() {
      timer.stop();
      start.setText("Start");
      start.setEnabled(true);
    }
  }
}

abstract class SwingWorker2<ValueT>
    implements BeforeAfterCallable<ValueT> {

  public SwingWorker2() {
    caller = new BeforeAfterCaller<ValueT>();
  }

  public SwingWorker2(Executor executor) {
    caller = new BeforeAfterCaller<ValueT>(executor);
  }

  public SwingWorker2(Executor executor, int timeoutMs) {
    caller = new BeforeAfterCaller<ValueT>(executor, timeoutMs);
  }

  private BeforeAfterCaller<ValueT> caller;

  public BeforeAfterFutureTask<ValueT> start() throws Exception {
    if (!SwingUtilities.isEventDispatchThread()) {
      throw new IllegalStateException(
          "start() must be called from the Event thread.");
    }
    return caller.submit(this);
  }

  public void before(AllowCallToProceed allowCallToProceed) throws Exception {
    before();
    allowCallToProceed.proceed();
  }

  public void after(final ValueT/*Object*/ futureResult,
                    final Throwable callThrowable
  ) throws Exception {
    // This is the other thread.
    SwingUtilities.invokeLater(new Runnable() { public void run() {
      try {
        if (callThrowable == null) {
          afterCallSuccess(futureResult);
        } else if (callThrowable instanceof InterruptedException) {
          afterInterrupted();
        } else {
          afterCallFailure(callThrowable);
        }
      } finally {
        afterFinally();
      }
    }});
  }

  // These are what you'll usually override.
  public void before() throws Exception {}
  public abstract ValueT/*Object*/ call() throws Exception;
  public void afterCallSuccess(ValueT/*Object*/ result) {}
  public void afterInterrupted() {}
  public void afterCallFailure(Throwable th) {}
  public void afterFinally() {}
}

DialogDemo2 using SwingWorker2

import java.util.concurrent.Executors;
import java.util.concurrent.Executor;
import java.util.concurrent.CountDownLatch;

import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.lang.reflect.InvocationTargetException;
import java.lang.ref.Reference;

/**
 * Put up a modal dialog,
 * then start a long-running task in a worker thread.
 */
class DialogDemo2 {

  public static void main(String[] args) throws Exception {
    // These are two of the three cases documented in the BeforeAfterCallable javadoc.
    runIt(true);
    runIt(false);
    System.exit(0);
  }

  private static volatile BeforeAfterFutureTask<String> future;

  private static void runIt(final boolean isModal) throws Exception {
    final CountDownLatch latch = new CountDownLatch(1);
    final DialogDemoWorker worker = new DialogDemoWorker(isModal, System.currentTimeMillis());
    SwingUtilities.invokeLater(new Runnable() { public void run() {
      try {
        future = worker.start();
        latch.countDown();
      } catch (Throwable th) {
        th.printStackTrace();
      }
    }});
    latch.await();
    future.get();
  }
}

//==============================================================================

class DialogDemoWorker extends SwingWorker2<String> {

  DialogDemoWorker(boolean isModal, long startTimeMs) {
    super();
    this.isModal = isModal;
    this.startTimeMs = startTimeMs;
  }

  private long startTimeMs;
  private JDialog dialog;
  private JProgressBar progressBar;
  private boolean isModal;

  private static final int progressSteps = 60;
  private static final int intervalMs = 20;
  private static final int endMs = 400;

  public void before(final AllowCallToProceed allowCallToProceed)
      throws Exception
  {
    invokeAndWaitMaybe(new Runnable() { public void run() {
      dialog = new JDialog();
      progressBar = new JProgressBar(0, progressSteps);
      dialog.setModal(isModal);
      if (isModal)
      {
        // There are other ways we could call allowCallToProceed.run(),
        // but this way demonstrates a pretty-demanding use case.
        dialog.addWindowListener(new WindowAdapter() {
          public void windowOpened(WindowEvent e) {
            // Announce we're ready for proceed() to be called.
            if (!allowCallToProceed.proceed()) {
              e.getWindow().setVisible(false);
            }
          }
        });
      }
      dialog.getContentPane().setLayout(new BorderLayout());
      dialog.getContentPane().add(progressBar);
      dialog.pack();
      dialog.setVisible(true);
    }});
    if (!isModal) allowCallToProceed.proceed();
  }

  // We are guaranteed that the dialog is open before this is called.
  public String/*Object*/ call() throws Exception {
    for (int count = 0; count <= progressSteps; ++count) {
      final int count2 = count;
      SwingUtilities.invokeLater(new Runnable() { public void run() {
        progressBar.setValue(count2);
      }});
      printTime();
      Thread.sleep(intervalMs);
    }
    return "OK";
  }

  private boolean firstTime = true;

  private void printTime() {
    if (firstTime) {
      firstTime = false;
      long now = System.currentTimeMillis();
      System.out.println((now - startTimeMs)
          + "ms from submit to first set of progressBar");
    }
  }

  // We are guaranteed that this is called after proceed() completes.
  public void afterFinally() {
    try {
      Thread.sleep(endMs);
    } catch (InterruptedException e) {
      // Tough.
    }
    SwingUtilities.invokeLater(new Runnable() { public void run() {
      dialog.setVisible(false);
    }});
  }

  static void invokeAndWaitMaybe(Runnable r)
      throws InvocationTargetException, InterruptedException {
    if (EventQueue.isDispatchThread()) {
      r.run();
    } else {
      SwingUtilities.invokeAndWait(r);
    }
  }
}


Download

Please email me if you would like to download the sources.


http://Yost.com/computers/java/before-after - this page
2005-07-12 Created
2005-10-11 Modified