Skip to content

Commit 7e91322

Browse files
iluwatarohbus
andauthored
Add explanation to Async Method Invocation pattern (iluwatar#1680)
* iluwatar#590 make the example more interesting and add an explanation * iluwatar#590 fix checkstyle Co-authored-by: Subhrodip Mohanta <hello@subho.xyz>
1 parent e9106cc commit 7e91322

File tree

2 files changed

+159
-17
lines changed
  • async-method-invocation

2 files changed

+159
-17
lines changed

async-method-invocation/README.md

Lines changed: 143 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,160 @@ tags:
99
---
1010

1111
## Intent
12-
Asynchronous method invocation is pattern where the calling thread
12+
13+
Asynchronous method invocation is a pattern where the calling thread
1314
is not blocked while waiting results of tasks. The pattern provides parallel
1415
processing of multiple independent tasks and retrieving the results via
1516
callbacks or waiting until everything is done.
1617

18+
## Explanation
19+
20+
Real world example
21+
22+
> Launching space rockets is an exciting business. The mission command gives an order to launch and
23+
> after some undetermined time, the rocket either launches successfully or fails miserably.
24+
25+
In plain words
26+
27+
> Asynchronous method invocation starts task processing and returns immediately before the task is
28+
> ready. The results of the task processing are returned to the caller later.
29+
30+
Wikipedia says
31+
32+
> In multithreaded computer programming, asynchronous method invocation (AMI), also known as
33+
> asynchronous method calls or the asynchronous pattern is a design pattern in which the call site
34+
> is not blocked while waiting for the called code to finish. Instead, the calling thread is
35+
> notified when the reply arrives. Polling for a reply is an undesired option.
36+
37+
**Programmatic Example**
38+
39+
In this example, we are launching space rockets and deploying lunar rovers.
40+
41+
The application demonstrates the async method invocation pattern. The key parts of the pattern are
42+
`AsyncResult` which is an intermediate container for an asynchronously evaluated value,
43+
`AsyncCallback` which can be provided to be executed on task completion and `AsyncExecutor` that
44+
manages the execution of the async tasks.
45+
46+
```java
47+
public interface AsyncResult<T> {
48+
boolean isCompleted();
49+
T getValue() throws ExecutionException;
50+
void await() throws InterruptedException;
51+
}
52+
```
53+
54+
```java
55+
public interface AsyncCallback<T> {
56+
void onComplete(T value, Optional<Exception> ex);
57+
}
58+
```
59+
60+
```java
61+
public interface AsyncExecutor {
62+
<T> AsyncResult<T> startProcess(Callable<T> task);
63+
<T> AsyncResult<T> startProcess(Callable<T> task, AsyncCallback<T> callback);
64+
<T> T endProcess(AsyncResult<T> asyncResult) throws ExecutionException, InterruptedException;
65+
}
66+
```
67+
68+
`ThreadAsyncExecutor` is an implementation of `AsyncExecutor`. Some of its key parts are highlighted
69+
next.
70+
71+
```java
72+
public class ThreadAsyncExecutor implements AsyncExecutor {
73+
74+
@Override
75+
public <T> AsyncResult<T> startProcess(Callable<T> task) {
76+
return startProcess(task, null);
77+
}
78+
79+
@Override
80+
public <T> AsyncResult<T> startProcess(Callable<T> task, AsyncCallback<T> callback) {
81+
var result = new CompletableResult<>(callback);
82+
new Thread(
83+
() -> {
84+
try {
85+
result.setValue(task.call());
86+
} catch (Exception ex) {
87+
result.setException(ex);
88+
}
89+
},
90+
"executor-" + idx.incrementAndGet())
91+
.start();
92+
return result;
93+
}
94+
95+
@Override
96+
public <T> T endProcess(AsyncResult<T> asyncResult)
97+
throws ExecutionException, InterruptedException {
98+
if (!asyncResult.isCompleted()) {
99+
asyncResult.await();
100+
}
101+
return asyncResult.getValue();
102+
}
103+
}
104+
```
105+
106+
Then we are ready to launch some rockets to see how everything works together.
107+
108+
```java
109+
public static void main(String[] args) throws Exception {
110+
// construct a new executor that will run async tasks
111+
var executor = new ThreadAsyncExecutor();
112+
113+
// start few async tasks with varying processing times, two last with callback handlers
114+
final var asyncResult1 = executor.startProcess(lazyval(10, 500));
115+
final var asyncResult2 = executor.startProcess(lazyval("test", 300));
116+
final var asyncResult3 = executor.startProcess(lazyval(50L, 700));
117+
final var asyncResult4 = executor.startProcess(lazyval(20, 400), callback("Deploying lunar rover"));
118+
final var asyncResult5 =
119+
executor.startProcess(lazyval("callback", 600), callback("Deploying lunar rover"));
120+
121+
// emulate processing in the current thread while async tasks are running in their own threads
122+
Thread.sleep(350); // Oh boy, we are working hard here
123+
log("Mission command is sipping coffee");
124+
125+
// wait for completion of the tasks
126+
final var result1 = executor.endProcess(asyncResult1);
127+
final var result2 = executor.endProcess(asyncResult2);
128+
final var result3 = executor.endProcess(asyncResult3);
129+
asyncResult4.await();
130+
asyncResult5.await();
131+
132+
// log the results of the tasks, callbacks log immediately when complete
133+
log("Space rocket <" + result1 + "> launch complete");
134+
log("Space rocket <" + result2 + "> launch complete");
135+
log("Space rocket <" + result3 + "> launch complete");
136+
}
137+
```
138+
139+
Here's the program console output.
140+
141+
```java
142+
21:47:08.227 [executor-2] INFO com.iluwatar.async.method.invocation.App - Space rocket <test> launched successfully
143+
21:47:08.269 [main] INFO com.iluwatar.async.method.invocation.App - Mission command is sipping coffee
144+
21:47:08.318 [executor-4] INFO com.iluwatar.async.method.invocation.App - Space rocket <20> launched successfully
145+
21:47:08.335 [executor-4] INFO com.iluwatar.async.method.invocation.App - Deploying lunar rover <20>
146+
21:47:08.414 [executor-1] INFO com.iluwatar.async.method.invocation.App - Space rocket <10> launched successfully
147+
21:47:08.519 [executor-5] INFO com.iluwatar.async.method.invocation.App - Space rocket <callback> launched successfully
148+
21:47:08.519 [executor-5] INFO com.iluwatar.async.method.invocation.App - Deploying lunar rover <callback>
149+
21:47:08.616 [executor-3] INFO com.iluwatar.async.method.invocation.App - Space rocket <50> launched successfully
150+
21:47:08.617 [main] INFO com.iluwatar.async.method.invocation.App - Space rocket <10> launch complete
151+
21:47:08.617 [main] INFO com.iluwatar.async.method.invocation.App - Space rocket <test> launch complete
152+
21:47:08.618 [main] INFO com.iluwatar.async.method.invocation.App - Space rocket <50> launch complete
153+
```
154+
17155
# Class diagram
156+
18157
![alt text](./etc/async-method-invocation.png "Async Method Invocation")
19158

20159
## Applicability
21-
Use async method invocation pattern when
160+
161+
Use the async method invocation pattern when
22162

23163
* You have multiple independent tasks that can run in parallel
24164
* You need to improve the performance of a group of sequential tasks
25-
* You have limited amount of processing capacity or long running tasks and the
26-
caller should not wait the tasks to be ready
165+
* You have a limited amount of processing capacity or long-running tasks and the caller should not wait for the tasks to be ready
27166

28167
## Real world examples
29168

async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@
2727
import lombok.extern.slf4j.Slf4j;
2828

2929
/**
30-
* This application demonstrates the async method invocation pattern. Key parts of the pattern are
31-
* <code>AsyncResult</code> which is an intermediate container for an asynchronously evaluated
32-
* value, <code>AsyncCallback</code> which can be provided to be executed on task completion and
33-
* <code>AsyncExecutor</code> that manages the execution of the async tasks.
30+
* In this example, we are launching space rockets and deploying lunar rovers.
31+
*
32+
* <p>The application demonstrates the async method invocation pattern. The key parts of the
33+
* pattern are <code>AsyncResult</code> which is an intermediate container for an asynchronously
34+
* evaluated value, <code>AsyncCallback</code> which can be provided to be executed on task
35+
* completion and <code>AsyncExecutor</code> that manages the execution of the async tasks.
3436
*
3537
* <p>The main method shows example flow of async invocations. The main thread starts multiple
3638
* tasks with variable durations and then continues its own work. When the main thread has done it's
@@ -68,13 +70,14 @@ public static void main(String[] args) throws Exception {
6870
final var asyncResult1 = executor.startProcess(lazyval(10, 500));
6971
final var asyncResult2 = executor.startProcess(lazyval("test", 300));
7072
final var asyncResult3 = executor.startProcess(lazyval(50L, 700));
71-
final var asyncResult4 = executor.startProcess(lazyval(20, 400), callback("Callback result 4"));
73+
final var asyncResult4 = executor.startProcess(lazyval(20, 400),
74+
callback("Deploying lunar rover"));
7275
final var asyncResult5 =
73-
executor.startProcess(lazyval("callback", 600), callback("Callback result 5"));
76+
executor.startProcess(lazyval("callback", 600), callback("Deploying lunar rover"));
7477

7578
// emulate processing in the current thread while async tasks are running in their own threads
76-
Thread.sleep(350); // Oh boy I'm working hard here
77-
log("Some hard work done");
79+
Thread.sleep(350); // Oh boy, we are working hard here
80+
log("Mission command is sipping coffee");
7881

7982
// wait for completion of the tasks
8083
final var result1 = executor.endProcess(asyncResult1);
@@ -84,9 +87,9 @@ public static void main(String[] args) throws Exception {
8487
asyncResult5.await();
8588

8689
// log the results of the tasks, callbacks log immediately when complete
87-
log("Result 1: " + result1);
88-
log("Result 2: " + result2);
89-
log("Result 3: " + result3);
90+
log("Space rocket <" + result1 + "> launch complete");
91+
log("Space rocket <" + result2 + "> launch complete");
92+
log("Space rocket <" + result3 + "> launch complete");
9093
}
9194

9295
/**
@@ -99,7 +102,7 @@ public static void main(String[] args) throws Exception {
99102
private static <T> Callable<T> lazyval(T value, long delayMillis) {
100103
return () -> {
101104
Thread.sleep(delayMillis);
102-
log("Task completed with: " + value);
105+
log("Space rocket <" + value + "> launched successfully");
103106
return value;
104107
};
105108
}
@@ -115,7 +118,7 @@ private static <T> AsyncCallback<T> callback(String name) {
115118
if (ex.isPresent()) {
116119
log(name + " failed: " + ex.map(Exception::getMessage).orElse(""));
117120
} else {
118-
log(name + ": " + value);
121+
log(name + " <" + value + ">");
119122
}
120123
};
121124
}

0 commit comments

Comments
 (0)