Drew
Drew

Reputation: 521

Run listener when task returns

I have:

  1. An ExecutorService that someone else has provided.
  2. A Runnable task that cleans up after itself when it is interrupted.
  3. A Runnable listener

My job is to run the task on the ExecutorService and then run the listener on the same ExecutorService sometime after the task has returned, either normally (via return) or by throwing an exception. I return to my client a Future and he (sometimes) calls cancel(true).

My first thought was to use Guava's ListenableFuture.addListener... but this executes the listener immediately after the future is cancelled, not after the task returns. However it does have the nice property that the listener is executed immediately if the task completes before the listener is added to the future. I've included this solution in the SSCCE below.

From the example below, what I want is the following:

Task Started
Task Canceling
Task Cancelled
**Listener Started**

What I actually get is:

Running
Canceling
**Listener**
Cancelled

In this example, I am allowed to change anything inside myMethod, the rest is provided to me.

public static void main(String[] args) {

    Runnable task = new Runnable() {
        public void run() {
            try {
                System.out.println("Task Started");
                interruptableWork();
                System.out.println("Task Completed");
            } catch (InterruptedException e) {
                System.out.println("Task Canceling");
                cleanup();
                System.out.println("Task Cancelled");
                Thread.currentThread().interrupt();
            }
        }

        private void interruptableWork() throws InterruptedException {
            TimeUnit.SECONDS.sleep(2);
        }

        private void cleanup() {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException ignored) {
            }
        }
    };

    Runnable listener = new Runnable() {
        public void run() {
            System.out.println("**Listener Started**");
        }
    };

    ExecutorService executor = Executors.newCachedThreadPool();

    Future<?> future = myMethod(task, listener, executor);

    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException ignored) {
    }

    future.cancel(true);

}

private static Future<?> myMethod(Runnable task, Runnable listener, ExecutorService executor) {
    ListeningExecutorService listeningExecutor = MoreExecutors.listeningDecorator(executor);
    ListenableFuture<?> future = listeningExecutor.submit(task);
    future.addListener(listener, executor);
    return future;
}

Upvotes: 3

Views: 1807

Answers (1)

Chris Povirk
Chris Povirk

Reputation: 3962

The first thing I'd try is to wrap the task and listener up in a Runnable:

Runnable wrappedTask = new Runnable() {
  public void run() {
    try {
      task.run();
    } finally {
      try {
        listener.run();
      } catch (RuntimeException e) 
        // log failure?
      }
    }
  }
};
executor.submit(wrappedTask);

(Of course, instead of logging listener failures, you could let the failure propagate. I chose to log (a) so that a listener failure doesn't override a task failure and (b) so that a listener failure doesn't override a task success.)

Upvotes: 3

Related Questions