从 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&lt;Void&gt; 而无法使用 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的原因时,我们可能会遇到未经检查的异常,即ErrorRuntimeException的子类,或者我们自定义的检查异常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() 方法?这不就是多捕获块吗? @Miguel getjoin 的不同之处在于将异常包装在 ExecutionException 而不是 CompletionException 中。这对catch 端没有任何改进。它还需要调用者处理InterruptedException,这使得它更加复杂。此外,由于Supplier 不能抛出已检查的ExecutionException,它必须与CompletionException 保持一致,然后get 将提取原因并将其重新包装在ExecutionException 中,这会降低效率.对于特殊情况,性能并不重要。但是get在这里的各个方面都比join差。

以上是关于从 CompletableFuture 抛出异常的主要内容,如果未能解决你的问题,请参考以下文章

从Java向MySQL添加数据抛出异常

CompletableFuture CompletableFuture.supplyAsync 异常处理

从 .* 的构造函数抛出的 boost 正则表达式异常

如何从 MFP 8 Java 适配器向客户端抛出用户异常?

从 SQL Server 函数向存储过程抛出异常

如果在“等待”之后抛出,则从任务中抛出的异常被吞下