Explain Codes LogoExplain Codes Logo

Waiting on a list of Future

java
async-programming
exception-handling
future-management
Alex KataevbyAlex Kataev·Sep 27, 2024
TLDR

Use the CompletableFuture.allOf() method to make sure all of your Futures have completed. You can then proceed to gather the results with a combination of the stream and map methods, which will take place once all futures have been finalized.

List<CompletableFuture<String>> futuresList = // ... CompletableFuture.allOf(futuresList.toArray(new CompletableFuture[0])).join(); List<String> results = futuresList.stream() .map(CompletableFuture::join) .collect(Collectors.toList());

This quickly enables waiting for multiple concurrent tasks and retrieving the results.

Managing futures efficiently

Up the ante using ExecutorCompletionService

If you wish to manage Futures more efficiently, consider using ExecutorCompletionService alongside an Executor. This enables fetching the futures as soon as they're completed without having to wait for slower tasks.

You can submit tasks using a loop and pull completed tasks using completionService.take(). Here's how:

ExecutorService executorService = Executors.newFixedThreadPool(10); CompletionService<String> completionService = new ExecutorCompletionService<>(executorService); List<Future<String>> futures = new ArrayList<>(); for(Runnable task : tasks) { futures.add(completionService.submit(task, null)); // like throwing tasks into a black hole } List<String> completedTasks = new ArrayList<>(); try { for (int i = 0; i < tasks.size(); i++) { Future<String> completedFuture = completionService.take(); // like getting a surprise each time you pull a future out completedTasks.add(completedFuture.get()); } } catch (Exception e) { // log and handle exception // perhaps abort the mission and cancel remaining futures with completedFuture.cancel(true) } executorService.shutdown(); // you can now call it a day

Face the exceptions head on

Exceptional situations are bound to occur when dealing with multiple concurrent tasks. With the help of a try-catch block inside the loop fetching from the CompletionService, exceptions can be managed effectively. In case of errors, cancel the remaining futures to save resources and maintain optimal performance.

Play safe with fast fail strategies

If your system needs to be quickly responsive to failed futures, consider CompletableFuture.anyOf(). This method awaits the completion of at least one Future, bailing out from waiting on the remainder.

Building robust and responsive applications

Save your resources by cancelling on exception

In an exception situation, consider whether it's necessary to cancel remaining futures. This can save significant processing resources especially important for systems where every CPU cycle is a precious commodity.

try { // ... } catch (Exception e) { // Log and handle exception for(Future<String> future : futures) { future.cancel(true); // Stop the impending doom of unnecessary processing } }

Enjoy async ride with CompletableFuture

In async operations, make use of CompletableFuture and its integrated exception handling mechanisms for building resilient systems.

CompletableFuture.supplyAsync(supplier) .exceptionally(ex -> "Oops! Houston, we have a problem: " + ex.getMessage()) .thenAccept(System.out::println); // every step monitored, if anything goes wrong, everyone will know right away!

Be wary of pitfalls with allOf

Bear in mind that CompletableFuture.allOf() may not be your friend when dealing with exceptions. It waits for all futures, which might not be suitable if immediate exception handling is what you are looking for.