CompletableFuture: Waiting for first one normally return?

前端 未结 3 685
滥情空心
滥情空心 2020-12-11 00:28

I have some CompletableFutures and I want to run them in parallel, waiting for the first that returns normally.

I know I can use

3条回答
  •  庸人自扰
    2020-12-11 00:55

    Well, that is a method what should be supported by the framework. First, I thought CompletionStage.applyToEither does something similar, but it turns out it doesnt. So I came up with this solution:

    public static  CompletionStage firstCompleted(Collection> stages) {
      final int count = stages.size();
      if (count <= 0) {
        throw new IllegalArgumentException("stages must not be empty");
      }
      final AtomicInteger settled = new AtomicInteger();
      final CompletableFuture future = new CompletableFuture();
      BiConsumer consumer = (val, exc) -> {
        if (exc == null) {
          future.complete(val);
        } else {
          if (settled.incrementAndGet() >= count) {
            // Complete with the last exception. You can aggregate all the exceptions if you wish.
            future.completeExceptionally(exc);
          }
        }
      };
      for (CompletionStage item : stages) {
        item.whenComplete(consumer);
      }
      return future;
    }
    

    To see it in action, here is some usage:

    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.List;
    import java.util.concurrent.CompletableFuture;
    import java.util.concurrent.CompletionStage;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.atomic.AtomicInteger;
    import java.util.function.BiConsumer;
    
    public class Main {
      public static  CompletionStage firstCompleted(Collection> stages) {
        final int count = stages.size();
        if (count <= 0) {
          throw new IllegalArgumentException("stages must not be empty");
        }
        final AtomicInteger settled = new AtomicInteger();
        final CompletableFuture future = new CompletableFuture();
        BiConsumer consumer = (val, exc) -> {
          if (exc == null) {
            future.complete(val);
          } else {
            if (settled.incrementAndGet() >= count) {
              // Complete with the last exception. You can aggregate all the exceptions if you wish.
              future.completeExceptionally(exc);
            }
          }
        };
        for (CompletionStage item : stages) {
          item.whenComplete(consumer);
        }
        return future;
      }
    
      private static final ScheduledExecutorService worker = Executors.newSingleThreadScheduledExecutor();
    
      public static  CompletionStage delayed(final U value, long delay) {
        CompletableFuture future = new CompletableFuture();
        worker.schedule(() -> {
          future.complete(value);
        }, delay, TimeUnit.MILLISECONDS);
        return future;
      }
      public static  CompletionStage delayedExceptionally(final Throwable value, long delay) {
        CompletableFuture future = new CompletableFuture();
        worker.schedule(() -> {
          future.completeExceptionally(value);
        }, delay, TimeUnit.MILLISECONDS);
        return future;
      }
    
      public static void main(String[] args) throws InterruptedException, ExecutionException {
        System.out.println("Started...");
    
        /*
        // Looks like applyToEither doesn't work as expected
        CompletableFuture a = CompletableFuture.completedFuture(99);
        CompletableFuture b = Main.completedExceptionally(new Exception("Exc")).toCompletableFuture();
        System.out.println(b.applyToEither(a, x -> x).get()); // throws Exc
        */
    
        try {
          List> futures = new ArrayList<>();
          futures.add(Main.delayedExceptionally(new Exception("Exception #1"), 100));
          futures.add(Main.delayedExceptionally(new Exception("Exception #2"), 200));
          futures.add(delayed(1, 1000));
          futures.add(Main.delayedExceptionally(new Exception("Exception #4"), 400));
          futures.add(delayed(2, 500));
          futures.add(Main.delayedExceptionally(new Exception("Exception #5"), 600));
          Integer value = firstCompleted(futures).toCompletableFuture().get();
          System.out.println("Completed normally: " + value);
        } catch (Exception ex) {
          System.out.println("Completed exceptionally");
          ex.printStackTrace();
        }
    
        try {
          List> futures = new ArrayList<>();
          futures.add(Main.delayedExceptionally(new Exception("Exception B#1"), 400));
          futures.add(Main.delayedExceptionally(new Exception("Exception B#2"), 200));
          Integer value = firstCompleted(futures).toCompletableFuture().get();
          System.out.println("Completed normally: " + value);
        } catch (Exception ex) {
          System.out.println("Completed exceptionally");
          ex.printStackTrace();
        }
    
        System.out.println("End...");
      }
    
    }
    

提交回复
热议问题