可再生未来:等待第一个正常返回?

2022-09-01 19:22:57

我有一些s,我想并行运行它们,等待第一个正常返回的。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();
}

答案 1

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

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;
}

它利用了这样一个事实,即 只有在所有期货完成(特殊或非)之后才会调用异常处理程序,并且未来只能完成一次(让特殊的事情放在一边)。当执行异常处理程序时,任何完成带有结果的未来(如果有)的尝试都已完成,因此,如果以前没有成功完成,则异常完成它的尝试只会成功。allOfobtrude…

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

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()); }

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

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


答案 2

考虑到:

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

    (它在多大程度上成功地做到了这一点是另一场辩论的主题;这一点仍然站得住脚,这无疑是该语言的主要目标之一。

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

    异常应始终重新附加到上面的层,或处理,或至少报告。具体来说,一个例外永远不应该被默默地吞噬。

  3. 应尽早报告错误。

    例如,查看运行时为了提供失败快速迭代器而经历的痛苦,如果在迭代时修改集合,则会引发 ConcurrentModificationException

  4. 忽略异常完成意味着a)您没有尽早报告错误,以及b)您可能根本不打算报告它。CompletableFuture

  5. 无法简单地等待第一个非异常完成,而是不得不被异常完成所困扰,这不会带来任何重大负担,因为您始终可以从列表中删除异常完成的项目(同时不要忘记报告失败,对吧?)并重复等待。

因此,如果Java中故意缺少所寻求的功能,我不会感到惊讶,我愿意争辩说它是理所当然地缺少的。

(对不起,索蒂里奥斯,没有规范的答案。


推荐