从 CompletableFuture 抛出异常
Posted
技术标签:
【中文标题】从 CompletableFuture 抛出异常【英文标题】:Throwing exception from CompletableFuture 【发布时间】:2017-11-08 15:33:34 【问题描述】:我有以下代码:
// How to throw the ServerException?
public void myFunc() throws ServerException
// Some code
CompletableFuture<A> a = CompletableFuture.supplyAsync(() ->
try
return someObj.someFunc();
catch(ServerException ex)
// throw ex; gives an error here.
));
// Some code
someFunc()
抛出 ServerException
。我不想在这里处理这个问题,而是将异常从someFunc()
抛出给myFunc()
的调用者。
【问题讨论】:
【参考方案1】:对于那些正在寻找使用 completableFuture 进行异常处理的其他方法的人
以下是处理解析错误为整数的几种方法:
1.使用handle
方法 - 它使您能够为异常提供默认值
CompletableFuture correctHandler = CompletableFuture.supplyAsync(() -> "A")
.thenApply(Integer::parseInt)
.handle((result, ex) ->
if (null != ex)
ex.printStackTrace();
return 0;
else
System.out.println("HANDLING " + result);
return result;
)
.thenAcceptAsync(s ->
System.out.println("CORRECT: " + s);
);
2。使用exceptionally
方法 - 类似于handle
,但不那么冗长
CompletableFuture parser = CompletableFuture.supplyAsync(() -> "1")
.thenApply(Integer::parseInt)
.exceptionally(t ->
t.printStackTrace();
return 0;
).thenAcceptAsync(s -> System.out.println("CORRECT value: " + s));
3.使用whenComplete
方法 - 使用这个方法将停止其轨道上的方法并且不执行下一个thenAcceptAsync
CompletableFuture correctHandler2 = CompletableFuture.supplyAsync(() -> "A")
.thenApply(Integer::parseInt)
.whenComplete((result, ex) ->
if (null != ex)
ex.printStackTrace();
)
.thenAcceptAsync(s ->
System.out.println("When Complete: " + s);
);
4.通过 completeExceptionally
public static CompletableFuture<Integer> converter(String convertMe)
CompletableFuture<Integer> future = new CompletableFuture<>();
try
future.complete(Integer.parseInt(convertMe));
catch (Exception ex)
future.completeExceptionally(ex);
return future;
【讨论】:
对于像我这样因为CompletableFuture<Void>
而无法使用 1、2 和 3 的人,只需返回一个 null
,其中需要一个 Void
类型的对象 :)我花了几个小时才弄清楚这个简单的事情。【参考方案2】:
即使别人的回答很好。但我给你另一种方法来在CompletableFuture
中抛出一个检查异常。
如果你不想在另一个线程中调用CompletableFuture
,你可以使用匿名类来处理它:
CompletableFuture<A> a = new CompletableFuture<A>()
try
complete(someObj.someFunc());
catch (ServerException ex)
completeExceptionally(ex);
;
如果你想在另一个线程中调用CompletableFuture
,你也可以使用匿名类来处理它,但是通过runAsync
运行方法:
CompletableFuture<A> a = new CompletableFuture<A>()
CompletableFuture.runAsync(() ->
try
complete(someObj.someFunc());
catch (ServerException ex)
completeExceptionally(ex);
);
;
【讨论】:
在匿名子类中根本不需要这样做。子类只会浪费资源。另见here 和here... @Holger 谢谢你,先生。我只把它写在我的脑海里。稍后我会看到。 @Holger 先生,我发现您的两个答案不同。我更喜欢你在这个问题中使用的第一个。因为它易于使用且非常清晰。【参考方案3】:我认为您应该将其包装成 RuntimeException
并扔掉:
throw new RuntimeException(ex);
或者许多是一个小实用程序会有所帮助:
static class Wrapper extends RuntimeException
private Wrapper(Throwable throwable)
super(throwable);
public static Wrapper wrap(Throwable throwable)
return new Wrapper(throwable);
public Throwable unwrap()
return getCause();
public static void go()
CompletableFuture<String> a = CompletableFuture.supplyAsync(() ->
try
throw new Exception("Just because");
catch (Exception ex)
throw Wrapper.wrap(ex);
);
a.join();
然后你可以unwrap
那个..
try
go();
catch (Wrapper w)
throw w.unwrap();
【讨论】:
我只需要抛出一个ServerException
。
@ayushgp 我没有看到默认流会发生这种情况,因为它们不允许检查异常......也许你可以包装那个而不是展开?
看起来你的 go()
方法永远不会抛出任何东西。我猜它缺少join()
电话。此外,Wrapper
提供的功能并不比Throwable.getCause()
提供的更多。如果不设置原因,我不会将异常包装到另一个异常中,因为它违反了约定并且不会打印正确的堆栈跟踪。
@DidierL 是的 go
只是为了证明一点,它确实不是很有用。另一方面,Wrapper
只是为了将检查的异常包装到运行时异常中。
@Eugene 我的意思是,在当前的go()
形式中,您的try
/catch
永远不会真正赶上Wrapper
。我觉得这很误导人。对于Wrapper
,我的意思是你应该调用super(Throwable)
,而不是定义你自己的字段,这样printStackTrace()
和getCause()
就会像这样一个包装器所期望的那样运行。【参考方案4】:
您的代码建议您稍后在同一方法中使用异步操作的结果,因此无论如何您都必须处理CompletionException
,因此一种处理方法是
public void myFunc() throws ServerException
// Some code
CompletableFuture<A> a = CompletableFuture.supplyAsync(() ->
try return someObj.someFunc();
catch(ServerException ex) throw new CompletionException(ex);
);
// Some code running in parallel to someFunc()
A resultOfA;
try
resultOfA = a.join();
catch(CompletionException ex)
try
throw ex.getCause();
catch(Error|RuntimeException|ServerException possible)
throw possible;
catch(Throwable impossible)
throw new AssertionError(impossible);
// some code using resultOfA
在调用join
时,在Supplier
的异步处理中抛出的所有异常都将被包装到CompletionException
中,除了我们已经包装在CompletionException
中的ServerException
。
当我们重新抛出CompletionException
的原因时,我们可能会遇到未经检查的异常,即Error
或RuntimeException
的子类,或者我们自定义的检查异常ServerException
。上面的代码使用 multi-catch 处理所有这些,这将重新抛出它们。由于getCause()
的声明返回类型是Throwable
,编译器要求我们处理该类型,尽管我们已经处理了所有可能的类型。直接的解决方案是将这个实际上不可能的 throwable 用 AssertionError
包裹起来。
或者,我们可以为自定义异常使用替代结果 future:
public void myFunc() throws ServerException
// Some code
CompletableFuture<ServerException> exception = new CompletableFuture<>();
CompletableFuture<A> a = CompletableFuture.supplyAsync(() ->
try return someObj.someFunc();
catch(ServerException ex)
exception.complete(ex);
throw new CompletionException(ex);
);
// Some code running in parallel to someFunc()
A resultOfA;
try
resultOfA = a.join();
catch(CompletionException ex)
if(exception.isDone()) throw exception.join();
throw ex;
// some code using resultOfA
此解决方案将以包装形式重新抛出所有“意外”的 throwable,但仅以通过 exception
未来传递的原始形式抛出自定义 ServerException
。请注意,在查询 exception
未来之前,我们必须确保 a
已经完成(例如首先调用 join()
),以避免出现竞争条件。
【讨论】:
Guava 有辅助方法。 Catch 看起来像这样: Throwables.throwIfUnchecked(e.getCause());抛出新的 RuntimeException(e.getCause()); @Holger 很好的答案!需要阻止连接以异步捕获和抛出异常 @Holger:为什么不使用 get() 方法?这不就是多捕获块吗? @Miguelget
与 join
的不同之处在于将异常包装在 ExecutionException
而不是 CompletionException
中。这对catch
端没有任何改进。它还需要调用者处理InterruptedException
,这使得它更加复杂。此外,由于Supplier
不能抛出已检查的ExecutionException
,它必须与CompletionException
保持一致,然后get
将提取原因并将其重新包装在ExecutionException
中,这会降低效率.对于特殊情况,性能并不重要。但是get
在这里的各个方面都比join
差。以上是关于从 CompletableFuture 抛出异常的主要内容,如果未能解决你的问题,请参考以下文章