Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add explanation to Async Method Invocation pattern #1680

Merged
merged 3 commits into from
Mar 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 143 additions & 4 deletions async-method-invocation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> {
boolean isCompleted();
T getValue() throws ExecutionException;
void await() throws InterruptedException;
}
```

```java
public interface AsyncCallback<T> {
void onComplete(T value, Optional<Exception> ex);
}
```

```java
public interface AsyncExecutor {
<T> AsyncResult<T> startProcess(Callable<T> task);
<T> AsyncResult<T> startProcess(Callable<T> task, AsyncCallback<T> callback);
<T> T endProcess(AsyncResult<T> 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 <T> AsyncResult<T> startProcess(Callable<T> task) {
return startProcess(task, null);
}

@Override
public <T> AsyncResult<T> startProcess(Callable<T> task, AsyncCallback<T> 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> T endProcess(AsyncResult<T> 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 <test> 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 <callback> launched successfully
21:47:08.519 [executor-5] INFO com.iluwatar.async.method.invocation.App - Deploying lunar rover <callback>
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 <test> 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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@
import lombok.extern.slf4j.Slf4j;

/**
* This application demonstrates the async method invocation pattern. Key parts of the pattern are
* <code>AsyncResult</code> which is an intermediate container for an asynchronously evaluated
* value, <code>AsyncCallback</code> which can be provided to be executed on task completion and
* <code>AsyncExecutor</code> that manages the execution of the async tasks.
* In this example, we are launching space rockets and deploying lunar rovers.
*
* <p>The application demonstrates the async method invocation pattern. The key parts of the
* pattern are <code>AsyncResult</code> which is an intermediate container for an asynchronously
* evaluated value, <code>AsyncCallback</code> which can be provided to be executed on task
* completion and <code>AsyncExecutor</code> that manages the execution of the async tasks.
*
* <p>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
Expand Down Expand Up @@ -68,13 +70,14 @@ public static void main(String[] args) throws Exception {
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);
Expand All @@ -84,9 +87,9 @@ public static void main(String[] args) throws Exception {
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");
}

/**
Expand All @@ -99,7 +102,7 @@ public static void main(String[] args) throws Exception {
private static <T> Callable<T> lazyval(T value, long delayMillis) {
return () -> {
Thread.sleep(delayMillis);
log("Task completed with: " + value);
log("Space rocket <" + value + "> launched successfully");
return value;
};
}
Expand All @@ -115,7 +118,7 @@ private static <T> AsyncCallback<T> callback(String name) {
if (ex.isPresent()) {
log(name + " failed: " + ex.map(Exception::getMessage).orElse(""));
} else {
log(name + ": " + value);
log(name + " <" + value + ">");
}
};
}
Expand Down