0

I've read that CompletableFuture has the ability to merge multiple futures with the runAfterBoth but what if I want to merge more than two?

CompletableFuture<Boolean> a = new CompletableFuture<>();
CompletableFuture<Boolean> b = new CompletableFuture<>();
CompletableFuture<Boolean> c = new CompletableFuture<>();

List<CompletableFuture<Boolean>> list = new LinkedList<>();

list.add(a);
list.add(b);
list.add(c);

// Could be any number
for (CompletableFuture<Boolean> f : list) {
   f.runAfter..
}

My use case is that I'm sending messages out to multiple sockets to locate a single object which may or may not be on any one of them.

I'm currently looking at this as a solution:

CompletableFuture<Boolean> a = new CompletableFuture<>();
CompletableFuture<Boolean> b = new CompletableFuture<>();
CompletableFuture<Boolean> c = new CompletableFuture<>();

List<CompletableFuture<Boolean>> list = new LinkedList<>();

list.add(a);
list.add(b);
list.add(c);

CompletableFuture<Boolean> result = new CompletableFuture<>();

Thread accept = new Thread(() -> {
   for (CompletableFuture<Boolean> f : list)
      if (f.join() != null)
         result.complete(f.join());
});

accept.start();

// Actual boolean value returned
result.get();

But it's kind of a mess. And in my case, I want to continue processing as soon as I get a valid result (not null) instead of waiting on the invalid results as well.

For example, a takes 5 seconds and the loop is waiting on it even though b has already completed in 2 seconds; but the loop doesn't know that because it's still waiting on a.

Is there a pattern to work with joining multiple asynchronous futures where I can respond immediately on a successful completion?

Another possibility:

public static class FutureUtil {
public static <T> CompletableFuture<T> anyOfNot(
   Collection<CompletableFuture<T>> collection,
   T value,
   T defaultValue)
{
   CompletableFuture<T> result = new CompletableFuture<>();

   new Thread(() -> {
      for (CompletableFuture<T> f : collection) {
         f.thenAccept((
            T r) -> {
            if ((r != null && !r.equals(value))
               || (value != null && !value.equals(r)))
               result.complete(r);
         });
      }

      try {
         for (CompletableFuture<T> f : collection)
            f.get();
      }
      catch (Exception ex) {
         result.completeExceptionally(ex);
      }

      result.complete(defaultValue);
   }).start();

   return result;
}
}

Example use:

CompletableFuture<Boolean> a = new CompletableFuture<>();
CompletableFuture<Boolean> b = new CompletableFuture<>();
CompletableFuture<Boolean> c = new CompletableFuture<>();

List<CompletableFuture<Boolean>> list = new LinkedList<>();

list.add(a);
list.add(b);
list.add(c);

CompletableFuture<Boolean> result = FutureUtil.anyOfNot(list, null, false);

result.get();
Zhro
  • 2,546
  • 2
  • 29
  • 39
  • Are you looking for [CompletableFuture.allOf()](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html#allOf-java.util.concurrent.CompletableFuture...-)? – teppic Dec 10 '16 at 07:46
  • Sort of. I won't want to wait on the other futures if one of them has already completed with a valid result (not null). – Zhro Dec 10 '16 at 08:37
  • Then maybe [`anyOf`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html#anyOf-java.util.concurrent.CompletableFuture...-) ? – Marco13 Dec 10 '16 at 09:17
  • `anyOf` would complete if a Boolean returned null. I only want the future which returns a non-null result. – Zhro Dec 10 '16 at 09:19
  • Similar question with useful answer [here](http://stackoverflow.com/questions/33913193/completablefuture-waiting-for-first-one-normally-return) – teppic Dec 10 '16 at 09:19
  • It's similar but still relies on `anyOf` which will complete on a null value. – Zhro Dec 10 '16 at 09:25

1 Answers1

1

If you know that at least one if the CFs in the List will complete with a non-null value, you can try this:

public static <T> CompletableFuture<T> firstNonNull(List<CompletableFuture<T>> completableFutures) {

    final CompletableFuture<T> completableFutureResult = new CompletableFuture<>();
    completableFutures.forEach(cf -> cf.thenAccept(v -> {
        if (v != null) {
            completableFutureResult.complete(v);
        }
    }));
    return completableFutureResult;
}

If there is no guarantee that at least one of the CFs will return a non-null value, you need something more complicated:

public static <T> CompletableFuture<T> firstNonNull(List<CompletableFuture<T>> completableFutures, T defaultValue) {

    final CompletableFuture<T> completableFutureResult = new CompletableFuture<>();
    completableFutures.forEach(cf -> cf.thenAccept(v -> {
        if (v != null) {
            completableFutureResult.complete(v);
        }
    }));
    //handling the situation where all the CFs returned null 
    CompletableFuture<Void> allCompleted = CompletableFuture
        .allOf((CompletableFuture<?>[]) completableFutures.toArray());
    allCompleted.thenRun(() -> {
        //checking first if any of the completed delivered a non-null value, to avoid race conditions with the block above 
        completableFutures.forEach(cf -> {
            final T result = cf.join();
            if (result != null) {
                completableFutureResult.complete(result);
            }
        });
        //if still not completed, completing with default value
        if ( !completableFutureResult.isDone()) {
            completableFutureResult.complete(defaultValue);
        }
    });
    return completableFutureResult;
}
Ruben
  • 3,986
  • 1
  • 21
  • 34
  • Thank you for your answer. But this is almost the same as the code sample at the end of my question but without the exception handling and default value. Note that you don't need to check for `isDone()` on an already completed future. – Zhro Dec 10 '16 at 17:08
  • In your solution you are creating an unnecessary `Thread`, and blocking it. I have shown you that you do not need an extra Thread to do the same work. _Note that you don't need to check for isDone() on an already completed future_ : well, you cannot know if it's completed at that stage. It can be that the `allOf` block is being executed before the `thenAccept`. You are right with the exception handling, but that was not part of your question. – Ruben Dec 10 '16 at 18:43
  • I get the following exception when trying to run your code. Can you confirm? `java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.util.concurrent.CompletableFuture` – Zhro Dec 10 '16 at 23:41
  • Which line? can you post an example? – Ruben Dec 11 '16 at 09:53
  • `(CompletableFuture>[]) completableFutures.toArray()` – Zhro Dec 11 '16 at 19:30
  • @Zhro change it to `completableFutures.stream().toArray(CompletableFuture[]::new);` and it should be fine. – Didier L Dec 14 '16 at 14:31