Kotlin 协程异常处理与上报机制

kotlinx.coroutines 项目中,异常的处理和上报遵循结构化并发(Structured Concurrency)的原则。异常并非简单的 try-catch 就能捕获,而是有一套从子协程到父协程层层上报的机制。

1. 异常传播的核心规则

协程构建器分为两类,它们处理异常的方式不同:

  • 自动传播(launch / produce):这些构建器将异常视为未捕获异常。它们会立即将异常向上传播给父协程,最终由 CoroutineExceptionHandler 或线程的未捕获异常处理器处理。
  • 暴露给用户(async / broadcast):这些构建器依赖用户最终消费异常。例如,通过 await()receive() 来捕获。如果未被消费,异常会被封装在 Deferred 对象中。

2. 异常上报的层层传递过程

当一个子协程抛出非 CancellationException 的异常时,会发生以下过程:

  1. 子协程失败:子协程捕获到异常。
  2. 上报父协程:子协程将异常传递给它的父 Job。
  3. 父协程取消:父协程接收到异常后,会首先取消自身,并同时取消其它的子协程。
  4. 继续向上传递:父协程继续将异常上报给它的父协程,直到到达根协程(Root Coroutine)。
  5. 最终处理:一旦到达根协程,异常将通过上下文中的 CoroutineExceptionHandler 进行处理。如果没有安装处理器,则由 platform 相关的默认机制处理(如 Android 的应用崩溃或 JVM 的标准错误输出)。

3. 监督机制(Supervision):阻断上报

有时我们不希望一个子协程的失败导致整个协程树崩溃,这时可以使用监督机制:

  • SupervisorJob:父协程使用 SupervisorJob 时,子协程的失败不会导致父协程取消,也不会影响兄弟协程。异常会直接由子协程尝试在自己的上下文中使用 CoroutineExceptionHandler 处理(如果存在)。
  • supervisorScope:创建一个监督作用域,其内部发生的异常不会向上传播给作用域外的父协程。

4. 异常聚合(Exception Aggregation)

如果多个子协程同时抛出异常:

  • 第一个异常胜出:第一个抛出的异常会被上报并处理。
  • 后续异常被抑制:在第一个异常之后发生的其它异常会被附加到第一个异常的 suppressed 列表中,不会丢失,但也不会触发多次处理逻辑。

5. 总结

在本项目中,try-catch 的有效性取决于你在哪里使用它:

  • launch 内部:可以捕获并内部消化。
  • asyncawait() 调用处:可以捕获该异步任务的异常。
  • 在协程外部或父协程级别:通常无法直接通过普通的 try-catch 拦截子协程的异常,必须依赖 CoroutineExceptionHandlersupervisorScope
,