将异常从异步传播到 CoroutineExceptionHandler

Propagate exception from async to CoroutineExceptionHandler

我有一个 async 协程,它可能会抛出异常。如何将异常传播到我的 CoroutineExceptionHandler?在 await 附近 try/catch 我能够捕获异常,但我似乎无法将处理传播到处理程序,无论上下文如何:

val handler = CoroutineExceptionHandler { _, e -> e.printStackTrace() }
val context = Executors.newSingleThreadExecutor().asCoroutineDispatcher() + handler
val scope = CoroutineScope(context)

val async = scope.async {
    throw Exception("Test exception")
}
runBlocking {
    try {
        async.await()
    } catch (e: Exception) {
        log.error("Exception thrown", e)
    }
    delay(1_000)
}

我试过从捕获中重新抛出异常,或者用以下一些结构包装它:

catch (e: Exception) {
    throw e
    // withContext(context) { throw e }
    // scope.launch { throw e }
    // supervisorScope { throw e }
}

可悲的是,其中 none 将其传播给了处理程序。我能以某种方式利用 CoroutineExceptionHandlerasync 协程吗?

不,你不能。

async returns 一个 Deferred,这就像一个未来。异常处理程序不适用,因为如果有异常,它是 Deferred 的 return 值。

来自文档:

In addition to that, async builder always catches all exceptions and represents them in the resulting Deferred object, so its CoroutineExceptionHandler has no effect either.

来源:https://kotlinlang.org/docs/exception-handling.html#coroutineexceptionhandler

有几种方法可以让它发挥作用。首先,正如 async documentation 中所述,它会在失败时取消父作业。这就是为什么 scope.launch { throw e } 什么都不做,范围在那个时候被取消,协程甚至没有启动。

然后,如 supervised coroutines documentation 中所述,要在受监督的上下文中处理未捕获的异常,您应该将处理程序安装到子上下文中。所以例如以下代码会将异常传递给您的处理程序

runBlocking {
   supervisorScope {
      launch(handler){
         async.await()
      }
   }
}   

另一种方法是将异常处理程序安装到协程的根范围内,例如像那样

runBlocking {
   GlobalScope.launch(handler) {
      async.await()
   }.join()
}

这里我使用GlobalScope进行演示,但它可以是任何活动的(未取消的)范围。

coroutine exception handling doc 和 运行 中有很多示例我建议查看。