我一直在玩CompletableFuture
并注意到一件奇怪的事情。
String url = "http://google.com";
CompletableFuture<String> contentsCF = readPageCF(url);
CompletableFuture<List<String>> linksCF = contentsCF.thenApply(_4_CompletableFutures::getLinks);
linksCF.thenAccept(list -> {
assertThat(list, not(empty()));
});
linksCF.get();
如果在我的thenAccept
调用中,断言失败,则不会传播异常。
我尝试了一些更丑陋的东西:
linksCF.thenAccept(list -> {
String a = null;
System.out.println(a.toString());
});
没有任何反应,也没有传播任何异常。我尝试使用handle
之类的方法以及与CompletableFutures
中的异常相关的其他方法,但是失败了 - 没有按预期传播异常。
当我调试CompletableFuture
时,它确实捕获了这样的异常:
final void internalComplete(T v, Throwable ex) {
if (result == null)
UNSAFE.compareAndSwapObject
(this, RESULT, null,
(ex == null) ? (v == null) ? NIL : v :
new AltResult((ex instanceof CompletionException) ? ex :
new CompletionException(ex)));
postComplete(); // help out even if not triggered
}
,别无其他。
我使用的是JDK 1.8.0_05 x64,Windows 7。
我在这里错过了什么吗?
答案 0 :(得分:24)
问题是您从未要求接收致电linksCF.thenAccept(..)
的结果。
您对linksCF.get()
的调用将等待链中执行的结果。但它只会返回当前linksCF未来的结果。这不包括断言的结果。
linksCF.thenAccept(..)
将返回一个新的CompletableFuture实例。要获得异常抛出调用get()
或在新返回的CompletableFuture实例上使用isCompletedExceptionally()
检查异常状态。
CompletableFuture<Void> acceptedCF = linksCF.thenAccept(list -> {
assertThat(list, not(empty()));
});
acceptedCF.exceptionally(th -> {
// will be executed when there is an exception.
System.out.println(th);
return null;
});
acceptedCF.get(); // will throw ExecutionException once results are available
替代?
CompletableFuture<List<String>> appliedCF = linksCF.thenApply(list -> {
assertThat(list, not(empty()));
return list;
});
appliedCF.exceptionally(th -> {
// will be executed when there is an exception.
System.out.println(th);
return Coolections.emptyList();
});
appliedCF.get(); // will throw ExecutionException once results are available
答案 1 :(得分:6)
虽然Gregor Koukkoullis(+1)基本上已经回答了这个问题,但我创建了MCVE来测试这个问题。
有几种方法可以获取导致内部问题的实际异常。但是,我不明白为什么在get
返回的未来调用thenAccept
应该是一个问题。有疑问,您还可以将thenApply
与身份功能一起使用,并使用一个漂亮的流畅模式,例如
List<String> list =
readPage().
thenApply(CompletableFutureTest::getLinks).
thenApply(t -> {
// check assertion here
return t;
}).get();
但也许有一个特殊原因可以避免这种情况。
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;
public class CompletableFutureTest
{
public static void main(String[] args)
throws InterruptedException, ExecutionException
{
CompletableFuture<String> contentsCF = readPage();
CompletableFuture<List<String>> linksCF =
contentsCF.thenApply(CompletableFutureTest::getLinks);
CompletableFuture<Void> completionStage = linksCF.thenAccept(list ->
{
String a = null;
System.out.println(a.toString());
});
// This will NOT cause an exception to be thrown, because
// the part that was passed to "thenAccept" will NOT be
// evaluated (it will be executed, but the exception will
// not show up)
List<String> result = linksCF.get();
System.out.println("Got "+result);
// This will cause the exception to be thrown and
// wrapped into an ExecutionException. The cause
// of this ExecutionException can be obtained:
try
{
completionStage.get();
}
catch (ExecutionException e)
{
System.out.println("Caught "+e);
Throwable cause = e.getCause();
System.out.println("cause: "+cause);
}
// Alternatively, the exception may be handled by
// the future directly:
completionStage.exceptionally(e ->
{
System.out.println("Future exceptionally finished: "+e);
return null;
});
try
{
completionStage.get();
}
catch (Throwable t)
{
System.out.println("Already handled by the future "+t);
}
}
private static List<String> getLinks(String s)
{
System.out.println("Getting links...");
List<String> links = new ArrayList<String>();
for (int i=0; i<10; i++)
{
links.add("link"+i);
}
dummySleep(1000);
return links;
}
private static CompletableFuture<String> readPage()
{
return CompletableFuture.supplyAsync(new Supplier<String>()
{
@Override
public String get()
{
System.out.println("Getting page...");
dummySleep(1000);
return "page";
}
});
}
private static void dummySleep(int ms)
{
try
{
Thread.sleep(ms);
}
catch (InterruptedException e)
{
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
}
答案 2 :(得分:2)
如果在我的thenAccept调用中,断言失败,则不会传播异常。
您向thenAccept()
注册的续约是linksCF
未来的独立任务。 linksCF
任务成功完成;报告没有错误。它有它的最终价值。 linksCF
引发的异常应仅表示产生linksCF
结果的问题;如果消耗结果的其他代码段抛出,则表示无法生成结果。
要观察延续中发生的异常,您必须观察延续的CompletableFuture
。
正确。但是1)我不应该被迫调用get() - 新构造的一个要点; 2)它包装在ExecutionException
中
如果您想使用thenAccept()
将结果移交给多个独立延续,该怎么办?如果其中一个延续投掷,为什么这会影响父母或其他延续?
如果您想将linksCF
视为链中的节点并观察链中发生的结果(以及任何异常),那么您应该在链中的最后一个链接上调用get()
您可以使用ExecutionException
代替join()
来避免选中get()
,这会将错误包装在未经检查的CompletionException
中(但它仍然被包装)。< / p>
答案 3 :(得分:2)
这里的答案帮助我在CompletableFuture中使用&#34; exceptionnaly&#34; 方法管理异常,但它错过了一个基本的例子,所以这里有一个,灵感来自Marco13答案:< / p>
/**
* Make a future launch an exception in the accept.
*
* This will simulate:
* - a readPage service called asynchronously that return a String after 1 second
* - a call to that service that uses the result then throw (eventually) an exception, to be processed by the exceptionnaly method.
*
*/
public class CompletableFutureTest2
{
public static void main(String[] args)
throws InterruptedException, ExecutionException
{
CompletableFuture<String> future = readPage();
CompletableFuture<Void> future2 = future.thenAccept(page->{
System.out.println(page);
throw new IllegalArgumentException("unexpected exception");
});
future2.exceptionally(e->{
e.printStackTrace(System.err);
return null;
});
}
private static CompletableFuture<String> readPage()
{
CompletableFuture<String> future = new CompletableFuture<>();
new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
// FUTURE: normal process
future.complete("page");
}).start();
return future;
}
}
要避免的错误是打电话&#34;例外&#34;在第一个未来(我的代码中的变量未来)而不是&#34; thenAccept&#34;返回的未来。其中包含可能抛出异常的lambda(我的代码中的变量future2)。
答案 4 :(得分:0)
与往常一样,了解CompletableFuture
的行为最好留给官方文档和博客。
实现CompletionStage的then...()
类的每个CompletableFuture
链接方法接受一个参数a CompletionStage
。传递的阶段取决于您链接的then...()
方法的顺序。再次,文档,但这里是that aforementioned blog。