从异步方法抛出异常是否合理?

时间:2019-01-16 16:46:30

标签: java multithreading asynchronous promise completable-future

用Java开发具有返回类型CompletableFuture的异步方法,我们希望所得的CF正常或异常完成,具体取决于该方法成功还是失败。

但是,考虑一下例如我的方法写入了AsynchronousChannel并在打开该通道时遇到了异常。它甚至还没有开始写作。因此,在这种情况下,我只是想让异常流向调用者。正确吗?

尽管调用方必须处理2种失败情况:1)异常,或2)被拒绝的诺言。

或者,我的方法应该捕获该异常并返回被拒绝的诺言吗?

3 个答案:

答案 0 :(得分:2)

我认为这都是有效的设计。 Datastax实际上是从第一种方法开始设计的,该方法在借用连接时被阻塞,然后切换到完全异步模型(https://docs.datastax.com/en/developer/java-driver/3.5/upgrade_guide/#3-0-4

作为datastax Java驱动程序的用户,我对该修复程序感到非常满意,因为它将api更改为真正的非阻塞(在您的示例中,即使打开一个通道也要付费)。

但是我不认为这里是对的...

答案 1 :(得分:2)

IMO,选项1)使得API难以使用,因为存在两种不同的错误传递路径:

  1. “同步”异常,其中该方法结束抛出的异常。
  2. “异步”异常,其中该方法返回CF,并附有异常。请注意,无法避免这种情况,因为总是有这样的情况:仅在异步路径启动后才发现错误(例如超时)。

程序员现在必须确保正确处理这两个路径,而不仅仅是一个。

有趣的是,C#和Javascript的行为都是始终通过返回的async / Task报告Promise函数体内抛出的异常,甚至对于在第一个await之前抛出的异常,永远不要以异常结束async函数调用。

即使在使用Unconfined调度程序时,对于Kotlin的协同程序也是如此

class SynchronousExceptionExamples {
    @Test
    fun example() {
        log.info("before launch")
        val job = GlobalScope.launch(Dispatchers.Unconfined) {
            log.info("before throw")
            throw Exception("an-error")
        }
        log.info("after launch")
        Thread.sleep(1000)
        assertTrue(job.isCancelled)
    }
}

会产生

6 [main] INFO SynchronousExceptionExamples - before launch
73 [main @coroutine#1] INFO SynchronousExceptionExamples - before throw
(...)
90 [main] INFO SynchronousExceptionExamples - after launch

请注意,main线程中有例外,但是launch以正确的Job结尾。

答案 2 :(得分:1)

从呼叫者的角度来看并没有太大的区别。无论哪种情况,无论是从方法中抛出还是在可完成的将来调用get()时,都可以看到异常原因。

我可能会争辩说,可完成的未来引发的异常应该是异步计算的异常,并且不会启动该计算。