Asynchronous method invocation is a pattern where the calling thread is not blocked while waiting results of tasks. The pattern provides parallel processing of multiple independent tasks and retrieving the results via callbacks or waiting until everything is done.
Real world example
Launching space rockets is an exciting business. The mission command gives an order to launch and after some undetermined time, the rocket either launches successfully or fails miserably.
In plain words
Asynchronous method invocation starts task processing and returns immediately before the task is ready. The results of the task processing are returned to the caller later.
Wikipedia says
In multithreaded computer programming, asynchronous method invocation (AMI), also known as asynchronous method calls or the asynchronous pattern is a design pattern in which the call site is not blocked while waiting for the called code to finish. Instead, the calling thread is notified when the reply arrives. Polling for a reply is an undesired option.
Programmatic Example
In this example, we are launching space rockets and deploying lunar rovers.
The application demonstrates the async method invocation pattern. The key parts of the pattern are
AsyncResult
which is an intermediate container for an asynchronously evaluated value,
AsyncCallback
which can be provided to be executed on task completion and AsyncExecutor
that
manages the execution of the async tasks.
1public interface AsyncResult<T> {
2 boolean isCompleted();
3 T getValue() throws ExecutionException;
4 void await() throws InterruptedException;
5}
1public interface AsyncCallback<T> {
2 void onComplete(T value, Optional<Exception> ex);
3}
1public interface AsyncExecutor {
2 <T> AsyncResult<T> startProcess(Callable<T> task);
3 <T> AsyncResult<T> startProcess(Callable<T> task, AsyncCallback<T> callback);
4 <T> T endProcess(AsyncResult<T> asyncResult) throws ExecutionException, InterruptedException;
5}
ThreadAsyncExecutor
is an implementation of AsyncExecutor
. Some of its key parts are highlighted
next.
1public class ThreadAsyncExecutor implements AsyncExecutor {
2
3 @Override
4 public <T> AsyncResult<T> startProcess(Callable<T> task) {
5 return startProcess(task, null);
6 }
7
8 @Override
9 public <T> AsyncResult<T> startProcess(Callable<T> task, AsyncCallback<T> callback) {
10 var result = new CompletableResult<>(callback);
11 new Thread(
12 () -> {
13 try {
14 result.setValue(task.call());
15 } catch (Exception ex) {
16 result.setException(ex);
17 }
18 },
19 "executor-" + idx.incrementAndGet())
20 .start();
21 return result;
22 }
23
24 @Override
25 public <T> T endProcess(AsyncResult<T> asyncResult)
26 throws ExecutionException, InterruptedException {
27 if (!asyncResult.isCompleted()) {
28 asyncResult.await();
29 }
30 return asyncResult.getValue();
31 }
32}
Then we are ready to launch some rockets to see how everything works together.
1public static void main(String[] args) throws Exception {
2 // construct a new executor that will run async tasks
3 var executor = new ThreadAsyncExecutor();
4
5 // start few async tasks with varying processing times, two last with callback handlers
6 final var asyncResult1 = executor.startProcess(lazyval(10, 500));
7 final var asyncResult2 = executor.startProcess(lazyval("test", 300));
8 final var asyncResult3 = executor.startProcess(lazyval(50L, 700));
9 final var asyncResult4 = executor.startProcess(lazyval(20, 400), callback("Deploying lunar rover"));
10 final var asyncResult5 =
11 executor.startProcess(lazyval("callback", 600), callback("Deploying lunar rover"));
12
13 // emulate processing in the current thread while async tasks are running in their own threads
14 Thread.sleep(350); // Oh boy, we are working hard here
15 log("Mission command is sipping coffee");
16
17 // wait for completion of the tasks
18 final var result1 = executor.endProcess(asyncResult1);
19 final var result2 = executor.endProcess(asyncResult2);
20 final var result3 = executor.endProcess(asyncResult3);
21 asyncResult4.await();
22 asyncResult5.await();
23
24 // log the results of the tasks, callbacks log immediately when complete
25 log("Space rocket <" + result1 + "> launch complete");
26 log("Space rocket <" + result2 + "> launch complete");
27 log("Space rocket <" + result3 + "> launch complete");
28}
Here’s the program console output.
121:47:08.227 [executor-2] INFO com.iluwatar.async.method.invocation.App - Space rocket <test> launched successfully
221:47:08.269 [main] INFO com.iluwatar.async.method.invocation.App - Mission command is sipping coffee
321:47:08.318 [executor-4] INFO com.iluwatar.async.method.invocation.App - Space rocket <20> launched successfully
421:47:08.335 [executor-4] INFO com.iluwatar.async.method.invocation.App - Deploying lunar rover <20>
521:47:08.414 [executor-1] INFO com.iluwatar.async.method.invocation.App - Space rocket <10> launched successfully
621:47:08.519 [executor-5] INFO com.iluwatar.async.method.invocation.App - Space rocket <callback> launched successfully
721:47:08.519 [executor-5] INFO com.iluwatar.async.method.invocation.App - Deploying lunar rover <callback>
821:47:08.616 [executor-3] INFO com.iluwatar.async.method.invocation.App - Space rocket <50> launched successfully
921:47:08.617 [main] INFO com.iluwatar.async.method.invocation.App - Space rocket <10> launch complete
1021:47:08.617 [main] INFO com.iluwatar.async.method.invocation.App - Space rocket <test> launch complete
1121:47:08.618 [main] INFO com.iluwatar.async.method.invocation.App - Space rocket <50> launch complete
Use the async method invocation pattern when