Search code examples
javamultithreadingasynchronouscompletable-future

CompletableFuture: anyOne which method is called and how can I get the desirable value


I need to call a method with different input parameters (like id) asynchronously, the method returns true or false, and the false output is not desirable, so I should wait for a true output. As soon as I get a true one, I do not care about the other calls, just I need to know the true output is corresponding with which input (id).

I call this piece of code, how should I know the input id of responded method? It might have a more completableFuture. The next question is in case of getting false, how can I skip and wait for getting true, because I need to recieve true from one of the myService.myMethod(int input)?

CompletableFuture<Boolean> completableFuture= CompletableFuture.supplyAsync(() -> myService.myMethod(1));
CompletableFuture<Boolean> completableFuture2= CompletableFuture.supplyAsync(() -> myService.myMethod(2));
CompletableFuture<Boolean> completableFuture3= CompletableFuture.supplyAsync(() -> myService.myMethod(3));
CompletableFuture<Boolean> completableFuture4= CompletableFuture.supplyAsync(() -> myService.myMethod(4));

CompletableFuture<Object> result =
        CompletableFuture.anyOf(completableFuture, completableFuture2,completableFuture3,,completableFuture4).thenApply(s -> s);

Solution

  • The fact that each result can be false makes this a bit tricky, because you can't abort too early. The following should work; it lets each CompletableFuture publish its results to a queue, as well as a sentinel value that indicates that all work is done. You then take the first element from the queue that was successful; if there is none, you know that all results are false if you read the sentinel value.

    record Result (int id, boolean value) {}
    final Result sentinel = new Result(Integer.MIN_VALUE, false);
    
    // one more than the number of results, for the sentinel
    BlockingDeque<Result> results = new LinkedBlockingDeque<>(5);
    // the number of results exactly
    CountDownLatch latch = new CountDownLatch(4);
    
    CompletableFuture.runAsync(() -> {
        Record record = new Record(1, myService.myMethod(1));
        results.add(record);
        latch.countDown();
    });
    // etc for 2-4
    CompletableFuture.runAsync(() -> {
        try {
            latch.await();
        } catch (InterruptedException e) {
            // log or something?
        } finally {
            results.add(sentinel);
        }
    });
    
    Result result = results.take();
    while (!result.value() && !result.equals(sentinel)) {
        result = results.take();
    }
    

    The sentinel is only added once each result has been published (due to the CountDownLatch), ensuring it's always the last element.

    When the loop ends, if result.equals(sentinel), all results were false. Otherwise, result contains the id for the first available successful result.