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:
- if
submit()
returns successfully,
then before()
has successfully executed
(as far as calling proceed()
),
and call()
has been called or will be called;
- if
call()
is called,
then after()
is called
after call()
finishes executing;
future.get()
blocks until
after()
has finished executing; and
future.get()
reflects
only what happened in call()
(ignoring any problems in before()
or after()
).
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 Throwable
s 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()
.
submit()
calls before()
;
before()
calls setvisible(true)
;
- the last-executing statement
in
before()
calls proceed()
;
- if
Executor
uses another thread:
before()
returns, and submit()
returns;
BeforeAfterCaller
calls call()
to do the work
and to advance the progress bar in the dialog;
BeforeAfterCaller
calls after()
;
after()
calls setvisible(false)
- if
Executor
uses the same thread:
before()
returns, and submit()
returns.
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.
submit()
calls before()
;
before()
adds a WindowListener
to the dialog;
- the last-executing statement in
before()
blocks
in the call to setvisible(true)
;
- a
WindowListener
in the AWT Event thread
calls proceed()
when the dialog actually becomes visible;
BeforeAfterCaller
calls call()
to do the work
and to advance the progress bar in the dialog;
BeforeAfterCaller
calls after()
;
after()
calls setvisible(false)
;
- the
setvisible(true)
call in before()
unblocks and returns;
before()
returns;
submit()
returns.
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