Class AsyncTrampoline
Static methods for asynchronous looping procedures without exhausting the stack.
When working with CompletionStage, it's often desirable to have a loop like construct
which keeps producing stages until some condition is met. Because continuations are asynchronous,
it's usually easiest to do this with a recursive approach:
CompletionStage<Integer> getNextNumber();
CompletionStage<Integer> getFirstOddNumber(int current) {
if (current % 2 != 0)
// found odd number
return CompletableFuture.completedFuture(current);
else
// get the next number and recurse
return getNextNumber().thenCompose(next -> getFirstOddNumber(next));
}
The problem with this is that if the implementation of getNextNumber happens to be synchronous
CompletionStage<Integer> getNextNumber() {
return CompletableFuture.completedFuture(random.nextInt());
}
then getFirstOddNumber can easily cause a stack overflow. This situation often happens when a
cache is put under an async API, and all the values are cached and returned immediately. This
could be avoided by scheduling the recursive calls back to a thread pool using
CompletionStage.thenComposeAsync(java.util.function.Function<? super T, ? extends java.util.concurrent.CompletionStage<U>>), however the overhead of the thread pool submissions may
be high and may cause unnecessary context switching.
The methods on this class ensure that the stack doesn't blow up - if multiple calls happen on the same thread they are queued and run in a loop. You could write the previous example like this:
CompletionStage<Integer> getFirstOddNumber(int initial) {
return AsyncTrampoline.asyncWhile(
i -> i % 2 == 0,
i -> getNextNumber(),
initial);
}
Though this class provides efficient methods for a few loop patterns, many are better represented
by the more expressive API available on AsyncIterator, which is also stack safe. For
example, the preceding snippet can be expressed as
AsyncIterator.generate(this::getNextNumber).find(i -> i % 2 != 0)
- See Also:
-
Method Summary
Modifier and TypeMethodDescriptionstatic <T> CompletionStage<T>asyncWhile(Predicate<? super T> shouldContinue, Function<? super T, ? extends CompletionStage<T>> fn, T initialValue) Repeatedly applies an asynchronous functionfnto a value untilshouldContinuereturnsfalse.static CompletionStage<Void>asyncWhile(Supplier<? extends CompletionStage<Boolean>> fn) Repeatedly uses the functionfnto produce aCompletionStageof a boolean, stopping when then boolean isfalse.
-
Method Details
-
asyncWhile
public static <T> CompletionStage<T> asyncWhile(Predicate<? super T> shouldContinue, Function<? super T, ? extends CompletionStage<T>> fn, T initialValue) Repeatedly applies an asynchronous functionfnto a value untilshouldContinuereturnsfalse. The asynchronous equivalent ofT loop(Predicate shouldContinue, Function fn, T initialValue) { T t = initialValue; while (shouldContinue.test(t)) { t = fn.apply(t); } return t; }Effectively produces
fn(seed).thenCompose(fn).thenCompose(fn)... .thenCompose(fn)until a value fails the predicate. Note that predicate will be applied on seed (like a while loop, the initial value is tested). If the predicate or fn throw an exception, or theCompletionStagereturned by fn completes exceptionally, iteration will stop and an exceptional stage will be returned.- Type Parameters:
T- the type of elements produced by the loop- Parameters:
shouldContinue- a predicate which will be applied to every intermediate T value (including theinitialValue) until it fails and looping stops.fn- the function for the loop body which produces a newCompletionStagebased on the result of the previous iteration.initialValue- the value that will initially be passed tofn, it will also be initially tested byshouldContinue- Returns:
- a
CompletionStagethat completes with the first value t such thatshouldContinue.test(T) == false, or with an exception if one was thrown.
-
asyncWhile
Repeatedly uses the functionfnto produce aCompletionStageof a boolean, stopping when then boolean isfalse. The asynchronous equivalent ofwhile(fn.get());. Generally, the function fn must perform some side effect for this method to be useful. If thefnthrows or produces an exceptionalCompletionStage, an exceptional stage will be returned.- Parameters:
fn- aSupplierof aCompletionStagethat indicates whether iteration should continue- Returns:
- a
CompletionStagethat is complete when a stage produced byfnhas returnedfalse, or with an exception if one was thrown
-