Search code examples
javaconcurrencycompletable-future

Run async actions in sequence


I have a sequence of I/O operations (DB, I/O devices...) I need to run in sequence.

@SafeVarargs
public final CompletableFuture<Boolean> execute(final Supplier<Boolean>... methods)
{
    CompletableFuture<Boolean> future = null;

    for (Supplier<Boolean> method : methods)
    {
        if (future == null)
        {
            future = CompletableFuture.supplyAsync(method, threadPool);
        }
        else
        {
            future.thenCombineAsync(CompletableFuture.supplyAsync(method, threadPool), (result, currentResult) -> result && currentResult,
                    threadPool);
        }
    }

    return future.exceptionally(this::onException);
}

My code executes randomly.

  1. What can I do to ensure order?
  2. How can I combine results in the end? If all were true, for instance?
  3. To apply a callback after everything is complete to check the result?

Solution

  • Your current solution calls supplyAsync() immediately and later tries to combine the results.

    If you want to guarantee sequential execution, you should use thenApply() or thenCompose() instead of thenCombine():

    for (Supplier<Boolean> method : methods)
    {
        if (future == null)
        {
            future = CompletableFuture.supplyAsync(method, threadPool);
        }
        else
        {
            future = future.thenApplyAsync(result -> result && method.get(), threadPool);
        }
    }
    

    Note that this will not call the method.get() on the next suppliers if any of them returns false, since && is short-circuiting. You could use a single & to force the call anyway, or swap the parameters.

    This already combines all the boolean results at the end. You can add anything on the resulting future after the loop, like more thenApply() calls, or a blocking join() call to retrieve the Boolean.

    Note that this loop can also easily be rewritten using streams:

    future = Arrays.stream(methods)
            .reduce(CompletableFuture.completedFuture(true),
                    (f, method) -> f.thenApplyAsync(result -> result && method.get()),
                    (f1, f2) -> f1.thenCombine(f2, (result1, result2) -> result1 && result2));