diff --git a/async-method-invocation/README.md b/async-method-invocation/README.md index 23910a631..0f56840af 100644 --- a/async-method-invocation/README.md +++ b/async-method-invocation/README.md @@ -9,21 +9,160 @@ tags: --- ## Intent -Asynchronous method invocation is pattern where the calling thread + +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. +## Explanation + +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. + +```java +public interface AsyncResult { + boolean isCompleted(); + T getValue() throws ExecutionException; + void await() throws InterruptedException; +} +``` + +```java +public interface AsyncCallback { + void onComplete(T value, Optional ex); +} +``` + +```java +public interface AsyncExecutor { + AsyncResult startProcess(Callable task); + AsyncResult startProcess(Callable task, AsyncCallback callback); + T endProcess(AsyncResult asyncResult) throws ExecutionException, InterruptedException; +} +``` + +`ThreadAsyncExecutor` is an implementation of `AsyncExecutor`. Some of its key parts are highlighted +next. + +```java +public class ThreadAsyncExecutor implements AsyncExecutor { + + @Override + public AsyncResult startProcess(Callable task) { + return startProcess(task, null); + } + + @Override + public AsyncResult startProcess(Callable task, AsyncCallback callback) { + var result = new CompletableResult<>(callback); + new Thread( + () -> { + try { + result.setValue(task.call()); + } catch (Exception ex) { + result.setException(ex); + } + }, + "executor-" + idx.incrementAndGet()) + .start(); + return result; + } + + @Override + public T endProcess(AsyncResult asyncResult) + throws ExecutionException, InterruptedException { + if (!asyncResult.isCompleted()) { + asyncResult.await(); + } + return asyncResult.getValue(); + } +} +``` + +Then we are ready to launch some rockets to see how everything works together. + +```java +public static void main(String[] args) throws Exception { + // construct a new executor that will run async tasks + var executor = new ThreadAsyncExecutor(); + + // start few async tasks with varying processing times, two last with callback handlers + final var asyncResult1 = executor.startProcess(lazyval(10, 500)); + final var asyncResult2 = executor.startProcess(lazyval("test", 300)); + final var asyncResult3 = executor.startProcess(lazyval(50L, 700)); + final var asyncResult4 = executor.startProcess(lazyval(20, 400), callback("Deploying lunar rover")); + final var asyncResult5 = + executor.startProcess(lazyval("callback", 600), callback("Deploying lunar rover")); + + // emulate processing in the current thread while async tasks are running in their own threads + Thread.sleep(350); // Oh boy, we are working hard here + log("Mission command is sipping coffee"); + + // wait for completion of the tasks + final var result1 = executor.endProcess(asyncResult1); + final var result2 = executor.endProcess(asyncResult2); + final var result3 = executor.endProcess(asyncResult3); + asyncResult4.await(); + asyncResult5.await(); + + // log the results of the tasks, callbacks log immediately when complete + log("Space rocket <" + result1 + "> launch complete"); + log("Space rocket <" + result2 + "> launch complete"); + log("Space rocket <" + result3 + "> launch complete"); +} +``` + +Here's the program console output. + +```java +21:47:08.227 [executor-2] INFO com.iluwatar.async.method.invocation.App - Space rocket launched successfully +21:47:08.269 [main] INFO com.iluwatar.async.method.invocation.App - Mission command is sipping coffee +21:47:08.318 [executor-4] INFO com.iluwatar.async.method.invocation.App - Space rocket <20> launched successfully +21:47:08.335 [executor-4] INFO com.iluwatar.async.method.invocation.App - Deploying lunar rover <20> +21:47:08.414 [executor-1] INFO com.iluwatar.async.method.invocation.App - Space rocket <10> launched successfully +21:47:08.519 [executor-5] INFO com.iluwatar.async.method.invocation.App - Space rocket launched successfully +21:47:08.519 [executor-5] INFO com.iluwatar.async.method.invocation.App - Deploying lunar rover +21:47:08.616 [executor-3] INFO com.iluwatar.async.method.invocation.App - Space rocket <50> launched successfully +21:47:08.617 [main] INFO com.iluwatar.async.method.invocation.App - Space rocket <10> launch complete +21:47:08.617 [main] INFO com.iluwatar.async.method.invocation.App - Space rocket launch complete +21:47:08.618 [main] INFO com.iluwatar.async.method.invocation.App - Space rocket <50> launch complete +``` + # Class diagram + ![alt text](./etc/async-method-invocation.png "Async Method Invocation") ## Applicability -Use async method invocation pattern when + +Use the async method invocation pattern when * You have multiple independent tasks that can run in parallel * You need to improve the performance of a group of sequential tasks -* You have limited amount of processing capacity or long running tasks and the - caller should not wait the tasks to be ready +* You have a limited amount of processing capacity or long-running tasks and the caller should not wait for the tasks to be ready ## Real world examples diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java index 04902a94c..b7d5e386b 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java @@ -27,10 +27,12 @@ import java.util.concurrent.Callable; import lombok.extern.slf4j.Slf4j; /** - * This application demonstrates the async method invocation pattern. 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. + * 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. * *

The main method shows example flow of async invocations. The main thread starts multiple * tasks with variable durations and then continues its own work. When the main thread has done it's @@ -68,13 +70,14 @@ public class App { final var asyncResult1 = executor.startProcess(lazyval(10, 500)); final var asyncResult2 = executor.startProcess(lazyval("test", 300)); final var asyncResult3 = executor.startProcess(lazyval(50L, 700)); - final var asyncResult4 = executor.startProcess(lazyval(20, 400), callback("Callback result 4")); + final var asyncResult4 = executor.startProcess(lazyval(20, 400), + callback("Deploying lunar rover")); final var asyncResult5 = - executor.startProcess(lazyval("callback", 600), callback("Callback result 5")); + executor.startProcess(lazyval("callback", 600), callback("Deploying lunar rover")); // emulate processing in the current thread while async tasks are running in their own threads - Thread.sleep(350); // Oh boy I'm working hard here - log("Some hard work done"); + Thread.sleep(350); // Oh boy, we are working hard here + log("Mission command is sipping coffee"); // wait for completion of the tasks final var result1 = executor.endProcess(asyncResult1); @@ -84,9 +87,9 @@ public class App { asyncResult5.await(); // log the results of the tasks, callbacks log immediately when complete - log("Result 1: " + result1); - log("Result 2: " + result2); - log("Result 3: " + result3); + log("Space rocket <" + result1 + "> launch complete"); + log("Space rocket <" + result2 + "> launch complete"); + log("Space rocket <" + result3 + "> launch complete"); } /** @@ -99,7 +102,7 @@ public class App { private static Callable lazyval(T value, long delayMillis) { return () -> { Thread.sleep(delayMillis); - log("Task completed with: " + value); + log("Space rocket <" + value + "> launched successfully"); return value; }; } @@ -115,7 +118,7 @@ public class App { if (ex.isPresent()) { log(name + " failed: " + ex.map(Exception::getMessage).orElse("")); } else { - log(name + ": " + value); + log(name + " <" + value + ">"); } }; }