CompletableFuture:等待第一个正常返回?

时间:2015-11-25 09:47:30

标签: java java-8 completable-future

我有一些CompletableFuture,我希望并行运行它们,等待第一个返回正常

我知道我可以使用CompletableFuture.anyOf等待第一个返回,但这将返回正常异常。我想忽略例外。

List<CompletableFuture<?>> futures = names.stream().map(
  (String name) ->
    CompletableFuture.supplyAsync(
      () ->
        // this calling may throw exceptions.
        new Task(name).run()
    )
).collect(Collectors.toList());
//FIXME Can not ignore exceptionally returned takes.
Future any = CompletableFuture.anyOf(futures.toArray(new CompletableFuture<?>[]{}));
try {
    logger.info(any.get().toString());
} catch (Exception e) {
    e.printStackTrace();
}

4 个答案:

答案 0 :(得分:8)

您可以使用以下帮助方法:

public static <T>
    CompletableFuture<T> anyOf(List<? extends CompletionStage<? extends T>> l) {

    CompletableFuture<T> f=new CompletableFuture<>();
    Consumer<T> complete=f::complete;
    l.forEach(s -> s.thenAccept(complete));
    return f;
}
您可以像这样使用

来证明它将忽略先前的异常但返回第一个提供的值:

List<CompletableFuture<String>> futures = Arrays.asList(
    CompletableFuture.supplyAsync(
        () -> { throw new RuntimeException("failing immediately"); }
    ),
    CompletableFuture.supplyAsync(
        () -> { LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(5));
          return "with 5s delay";
        }),
    CompletableFuture.supplyAsync(
        () -> { LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(10));
          return "with 10s delay";
        })
);
CompletableFuture<String> c = anyOf(futures);
logger.info(c.join());

此解决方案的一个缺点是,如果所有期货异常完成,它将永远不会完成。一个解决方案,如果成功计算将提供第一个值,但如果根本没有成功计算则会异常失败,则需要更多参与:

public static <T>
    CompletableFuture<T> anyOf(List<? extends CompletionStage<? extends T>> l) {

    CompletableFuture<T> f=new CompletableFuture<>();
    Consumer<T> complete=f::complete;
    CompletableFuture.allOf(
        l.stream().map(s -> s.thenAccept(complete)).toArray(CompletableFuture<?>[]::new)
    ).exceptionally(ex -> { f.completeExceptionally(ex); return null; });
    return f;
}

它利用了这样一个事实:allOf的特殊处理程序仅在所有期货完成后(异常或非特定)被调用,并且未来只能完成一次(让obtrude…之类的特殊事物放在一边)。当执行异常处理程序时,如果有结果,则完成任何使用结果完成未来的尝试,因此如果没有先前成功完成,则异常地完成它的尝试仅成功。

它可以与第一个解决方案完全相同的方式使用,并且只有在所有计算都失败时才会表现出不同的行为,例如:

List<CompletableFuture<String>> futures = Arrays.asList(
    CompletableFuture.supplyAsync(
        () -> { throw new RuntimeException("failing immediately"); }
    ),
    CompletableFuture.supplyAsync(
        // delayed to demonstrate that the solution will wait for all completions
        // to ensure it doesn't miss a possible successful computation
        () -> { LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(5));
            throw new RuntimeException("failing later"); }
    )
);
CompletableFuture<String> c = anyOf(futures);
try { logger.info(c.join()); }
catch(CompletionException ex) { logger.severe(ex.toString()); }

上面的示例使用延迟表明解决方案将在没有成功时等待所有完成,而this example on ideone将演示稍后的成功将如何将结果转化为成功。请注意,由于Ideones缓存结果,您可能不会注意到延迟。

请注意,如果所有期货都失败,则无法保证会报告哪些例外情况。由于它在错误的情况下等待所有完成,任何可以使它达到最终结果。

答案 1 :(得分:4)

考虑到:

  1. Java哲学的基础之一是防止或阻止糟糕的编程实践。

    (它在多大程度上成功地这样做是另一场辩论的主题;这一点仍然表明这无疑是该语言的主要目标之一。)

  2. 忽略异常是一种非常糟糕的做法。

    异常应该始终是重新抛出到上面的层,或处理,或至少报告。具体,永远不会被悄悄吞下。

  3. 应尽早报告错误。

    例如,

    ,查看运行时经历的痛苦,以便提供 fail fast 迭代器,如果在迭代时修改了集合,则会抛出 ConcurrentModificationException 。 / SUP>

  4. 忽略一个异常完成的CompletableFuture意味着a)您没有尽快报告错误,并且b)您可能计划不报告错误所有

  5. 无法简单地等待第一次非特殊完成而无需受到特殊完成的困扰并不会造成任何重大负担,因为您始终可以从列表中删除异常完成的项目(同时不忘记报失败,对吗?)并重复等待。

  6. 如果所追求的功能在Java中有意缺失,我不会感到惊讶,我愿意争辩说正确地缺失。

    (抱歉Sotirios,没有规范的答案。)

答案 2 :(得分:1)

嗯,这是框架应该支持的方法。首先,我认为CompletionStage.applyToEither做了类似的事情,但事实证明它没有。所以我提出了这个解决方案:

public static <U> CompletionStage<U> firstCompleted(Collection<CompletionStage<U>> stages) {
  final int count = stages.size();
  if (count <= 0) {
    throw new IllegalArgumentException("stages must not be empty");
  }
  final AtomicInteger settled = new AtomicInteger();
  final CompletableFuture<U> future = new CompletableFuture<U>();
  BiConsumer<U, Throwable> 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<U> item : stages) {
    item.whenComplete(consumer);
  }
  return future;
}

要看到它的实际效果,这里有一些用法:

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 <U> CompletionStage<U> firstCompleted(Collection<CompletionStage<U>> stages) {
    final int count = stages.size();
    if (count <= 0) {
      throw new IllegalArgumentException("stages must not be empty");
    }
    final AtomicInteger settled = new AtomicInteger();
    final CompletableFuture<U> future = new CompletableFuture<U>();
    BiConsumer<U, Throwable> 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<U> item : stages) {
      item.whenComplete(consumer);
    }
    return future;
  }

  private static final ScheduledExecutorService worker = Executors.newSingleThreadScheduledExecutor();

  public static <U> CompletionStage<U> delayed(final U value, long delay) {
    CompletableFuture<U> future = new CompletableFuture<U>();
    worker.schedule(() -> {
      future.complete(value);
    }, delay, TimeUnit.MILLISECONDS);
    return future;
  }
  public static <U> CompletionStage<U> delayedExceptionally(final Throwable value, long delay) {
    CompletableFuture<U> future = new CompletableFuture<U>();
    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<Integer> a = CompletableFuture.completedFuture(99);
    CompletableFuture<Integer> b = Main.<Integer>completedExceptionally(new Exception("Exc")).toCompletableFuture();
    System.out.println(b.applyToEither(a, x -> x).get()); // throws Exc
    */

    try {
      List<CompletionStage<Integer>> futures = new ArrayList<>();
      futures.add(Main.<Integer>delayedExceptionally(new Exception("Exception #1"), 100));
      futures.add(Main.<Integer>delayedExceptionally(new Exception("Exception #2"), 200));
      futures.add(delayed(1, 1000));
      futures.add(Main.<Integer>delayedExceptionally(new Exception("Exception #4"), 400));
      futures.add(delayed(2, 500));
      futures.add(Main.<Integer>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<CompletionStage<Integer>> futures = new ArrayList<>();
      futures.add(Main.<Integer>delayedExceptionally(new Exception("Exception B#1"), 400));
      futures.add(Main.<Integer>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...");
  }

}

答案 3 :(得分:0)

对上面的代码做了一些修改,允许测试第一个结果是否符合预期。

public class MyTask implements Callable<String> {

    @Override
    public String call() throws Exception {
        int randomNum = ThreadLocalRandom.current().nextInt(5, 20 + 1);
        for (int i = 0; i < randomNum; i++) {
            TimeUnit.SECONDS.sleep(1);
        }
        return "MyTest" + randomNum;
    }
}


public class CompletableFutureUtils {

    private static <T> T resolve(FutureTask<T> futureTask) {
        try {
            futureTask.run();
            return futureTask.get();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static <V> boolean predicate(Predicate<V> predicate, V v) {
        try {
            return predicate.test(v);
        } catch (Exception e) {
            return false;
        }
    }

    public static <T> void cancel(List<FutureTask<T>> futureTasks) {
        if (futureTasks != null && futureTasks.isEmpty() == false) {
            futureTasks.stream().filter(f -> f.isDone() == false).forEach(f -> f.cancel(true));
        }
    }

    public static <V> CompletableFuture<V> supplyAsync(List<FutureTask<V>> futureTasks, Predicate<V> predicate) {
        return supplyAsync(futureTasks, predicate, null);
    }

    public static <V> CompletableFuture<V> supplyAsync(List<FutureTask<V>> futureTasks, Predicate<V> predicate,
            Executor executor) {
        final int count = futureTasks.size();
        final AtomicInteger settled = new AtomicInteger();
        final CompletableFuture<V> result = new CompletableFuture<V>();
        final BiConsumer<V, Throwable> action = (value, ex) -> {
            settled.incrementAndGet();
            if (result.isDone() == false) {
                if (ex == null) {
                    if (predicate(predicate, value)) {
                        result.complete(value);
                        cancel(futureTasks);
                    } else if (settled.get() >= count) {
                        result.complete(null);
                    }
                } else if (settled.get() >= count) {
                    result.completeExceptionally(ex);
                }
            }
        };
        for (FutureTask<V> futureTask : futureTasks) {
            if (executor != null) {
                CompletableFuture.supplyAsync(() -> resolve(futureTask), executor).whenCompleteAsync(action, executor);
            } else {
                CompletableFuture.supplyAsync(() -> resolve(futureTask)).whenCompleteAsync(action);
            }
        }
        return result;
    }
}

public class DemoApplication {
    public static void main(String[] args) {
        List<FutureTask<String>> tasks = new ArrayList<FutureTask<String>>();
        for (int i = 0; i < 2; i++) {
            FutureTask<String> task = new FutureTask<String>(new MyTask());
            tasks.add(task);
        }
        Predicate<String> test = (s) -> true;
        CompletableFuture<String> result = CompletableFutureUtils.supplyAsync(tasks, test);
        try {
            String s = result.get(20, TimeUnit.SECONDS);
            System.out.println("result=" + s);
        } catch (Exception e) {
            e.printStackTrace();
            CompletableFutureUtils.cancel(tasks);
        }
    }
}

调用CompletableFutureUtils.cancel(tasks);非常重要,当超时发生时,它会取消后台任务。