April Papajohn
April Papajohn

Reputation: 479

Does thenRunAsync (as opposed to thenRun) make any difference if chained after a runAsync call?

In the following code, does it make any difference to call thenRunAsync? Should I just call thenRun instead?

CompletableFuture.runAsync(this::doWork , executorService)
     .thenRunAsync(this::handleSuccess);

Elaborating in response to the comments: If I were to use this code instead,

CompletableFuture.runAsync(this::doWork , executorService)
     .thenRun(this::handleSuccess);

would it behave any differently?

In both cases, the behavior is non-blocking, and the second task won't run until the first completes, as far as I understand it, anyhow.

Upvotes: 10

Views: 8626

Answers (1)

Holger
Holger

Reputation: 298103

The method thenRun allows the execution of the Runnable directly in the caller’s thread, if the CompletableFuture has already completed. Since even in a direct invocation chain like CompletableFuture.runAsync(…).thenRun(…); there’s the possibility that the asynchronous task has already completed by the time thenRun is invoked, there’s the possibility that the dependent action is executed in the caller’s thread, unlike thenRunAsync which will always use the default (or provided) executor.

So in one sentence, yes, it makes a difference.


By the way, using thenRunAsync (the single argument version) will not execute the action using the Executor supplied to the initial factory call, but the default Executor.

You can easily compare the different behaviors:

public static void main(String[] args) {
    ExecutorService e=Executors.newSingleThreadExecutor(r -> new Thread(r, "sole thread"));
    CompletableFuture<?> f=CompletableFuture.runAsync(()->{}, e);
    f.join();
    f.thenRun(()->System.out.println("thenRun:\t"+Thread.currentThread()));
    f.thenRunAsync(()->System.out.println("thenRunAsync:\t"+Thread.currentThread()));
    f.thenRunAsync(()->System.out.println("thenRunAsync+e:\t"+Thread.currentThread()), e);
    e.shutdown();
}

will print

thenRun:        Thread[main,5,main]
thenRunAsync:   Thread[ForkJoinPool.commonPool-worker-1,5,main]
thenRunAsync+e: Thread[sole thread,5,main]

whereas

public static void main(String[] args) {
   ExecutorService e=Executors.newSingleThreadExecutor(r -> new Thread(r, "sole thread"));
   CompletableFuture<?>f=CompletableFuture.runAsync(()->LockSupport.parkNanos((int)1e9),e);
   f.thenRun(()->System.out.println("thenRun:\t"+Thread.currentThread()));
   f.thenRunAsync(()->System.out.println("thenRunAsync:\t"+Thread.currentThread()));
   f.thenRunAsync(()->System.out.println("thenRunAsync+e:\t"+Thread.currentThread()), e);
   LockSupport.parkNanos((int)2e9);
   e.shutdown();
}

will print

thenRun:        Thread[sole thread,5,main]
thenRunAsync:   Thread[ForkJoinPool.commonPool-worker-1,5,main]
thenRunAsync+e: Thread[sole thread,5,main]

So thenRun may execute the action in either, the caller’s thread or the Executor’s thread whereas the single-argument thenRunAsync will always use the Fork/Join pool and only the two argument thenRunAsync will always use the provided executor.

Upvotes: 19

Related Questions