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 f59f831b2..de4dfe926 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 @@ -2,30 +2,76 @@ package com.iluwatar.async.method.invocation; import java.util.concurrent.Callable; +/** + *

+ * 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. + *

+ *

+ * 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 job it collects + * the results of the async tasks. Two of the tasks are handled with callbacks, meaning the callbacks are + * executed immediately when the tasks complete. + *

+ *

+ * Noteworthy difference of thread usage between the async results and callbacks is that the async results + * are collected in the main thread but the callbacks are executed within the worker threads. This should be + * noted when working with thread pools. + *

+ *

+ * Java provides its own implementations of async method invocation pattern. FutureTask, CompletableFuture + * and ExecutorService are the real world implementations of this pattern. But due to the nature of parallel + * programming, the implementations are not trivial. This example does not take all possible scenarios into + * account but rather provides a simple version that helps to understand the pattern. + *

+ * + * @see AsyncResult + * @see AsyncCallback + * @see AsyncExecutor + * + * @see java.util.concurrent.FutureTask + * @see java.util.concurrent.CompletableFuture + * @see java.util.concurrent.ExecutorService + */ public class App { public static void main(String[] args) throws Exception { + // construct a new executor that will run async tasks AsyncExecutor executor = new ThreadAsyncExecutor(); + + // start few async tasks with varying processing times, two last with callback handlers AsyncResult asyncResult1 = executor.startProcess(lazyval(10, 500)); AsyncResult asyncResult2 = executor.startProcess(lazyval("test", 300)); AsyncResult asyncResult3 = executor.startProcess(lazyval(50L, 700)); AsyncResult asyncResult4 = executor.startProcess(lazyval(20, 400), callback("Callback result 4")); AsyncResult asyncResult5 = executor.startProcess(lazyval("callback", 600), callback("Callback result 5")); + // 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"); + // wait for completion of the tasks Integer result1 = executor.endProcess(asyncResult1); String result2 = executor.endProcess(asyncResult2); Long result3 = executor.endProcess(asyncResult3); asyncResult4.await(); 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); } + /** + * Creates a callable that lazily evaluates to given value with artificial delay. + * + * @param value value to evaluate + * @param delayMillis artificial delay in milliseconds + * @return new callable for lazy evaluation + */ private static Callable lazyval(T value, long delayMillis) { return () -> { Thread.sleep(delayMillis); @@ -34,6 +80,12 @@ public class App { }; } + /** + * Creates a simple callback that logs the complete status of the async result. + * + * @param name callback name + * @return new async callback + */ private static AsyncCallback callback(String name) { return (value, ex) -> { if (ex.isPresent()) { diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java index 0477f70e1..067b79d43 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java @@ -4,6 +4,12 @@ import java.util.Optional; public interface AsyncCallback { + /** + * Complete handler which is executed when async task is completed or fails execution. + * + * @param value the evaluated value from async task, undefined when execution fails + * @param ex empty value if execution succeeds, some exception if executions fails + */ void onComplete(T value, Optional ex); } diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java index dd23a8bfb..4bf837a4f 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java @@ -5,10 +5,33 @@ import java.util.concurrent.ExecutionException; public interface AsyncExecutor { + /** + * Starts processing of an async task. Returns immediately with async result. + * + * @param task task to be executed asynchronously + * @return async result for the task + */ AsyncResult startProcess(Callable task); + /** + * Starts processing of an async task. Returns immediately with async result. Executes callback + * when the task is completed. + * + * @param task task to be executed asynchronously + * @param callback callback to be executed on task completion + * @return async result for the task + */ AsyncResult startProcess(Callable task, AsyncCallback callback); + /** + * Ends processing of an async task. Blocks the current thread if necessary and returns the + * evaluated value of the completed task. + * + * @param asyncResult async result of a task + * @return evaluated value of the completed task + * @throws ExecutionException if execution has failed, containing the root cause + * @throws InterruptedException if the execution is interrupted + */ T endProcess(AsyncResult asyncResult) throws ExecutionException, InterruptedException; } diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java index 5bf6145b8..689095e9d 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java @@ -4,9 +4,26 @@ import java.util.concurrent.ExecutionException; public interface AsyncResult { + /** + * Status of the async task execution. + * + * @return true if execution is completed or failed + */ boolean isCompleted(); + /** + * Gets the value of completed async task. + * + * @return evaluated value or throws ExecutionException if execution has failed + * @throws ExecutionException if execution has failed, containing the root cause + * @throws IllegalStateException if execution is not completed + */ T getValue() throws ExecutionException; + /** + * Blocks the current thread until the async task is completed. + * + * @throws InterruptedException if the execution is interrupted + */ void await() throws InterruptedException; } diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java index b368e284d..18b27c3b6 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java @@ -5,8 +5,12 @@ import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicInteger; +/** + * Implementation of async executor that creates a new thread for every task. + */ public class ThreadAsyncExecutor implements AsyncExecutor { + /** Index for thread naming */ private final AtomicInteger idx = new AtomicInteger(0); @Override @@ -37,6 +41,14 @@ public class ThreadAsyncExecutor implements AsyncExecutor { } } + /** + * Simple implementation of async result that allows completing it successfully with a value + * or exceptionally with an exception. A really simplified version from its real life cousins + * FutureTask and CompletableFuture. + * + * @see java.util.concurrent.FutureTask + * @see java.util.concurrent.CompletableFuture + */ private static class CompletableResult implements AsyncResult { static final int RUNNING = 1; @@ -55,6 +67,12 @@ public class ThreadAsyncExecutor implements AsyncExecutor { this.callback = Optional.ofNullable(callback); } + /** + * Sets the value from successful execution and executes callback if available. Notifies + * any thread waiting for completion. + * + * @param value value of the evaluated task + */ void setValue(T value) { this.value = value; this.state = COMPLETED; @@ -64,6 +82,12 @@ public class ThreadAsyncExecutor implements AsyncExecutor { } } + /** + * Sets the exception from failed execution and executes callback if available. Notifies + * any thread waiting for completion. + * + * @param exception exception of the failed task + */ void setException(Exception exception) { this.exception = exception; this.state = FAILED;