Kotlin 协程异常机制
2024-2-22
| 2024-9-2
0  |  Read Time 0 min
type
status
date
slug
summary
tags
category
icon
password

异常的传播

由于 CoroutineScope 可以创建协程,并且您可以在协程内创建更多协程,因此会创建隐式任务层次结构。在任务层次结构中,每个协程都有一个父级,可以是 CoroutineScope 或另一个协程。新生成的协程的 CoroutineContext 与父级的 CoroutineContext 有继承关系。每个任务 Job 都有自己的生命周期,子任务的生命周期受父任务的生命周期控制,比如如果父级 job 关闭,子协程 job 也会被取消。
 
当协程因异常而失败时,它会优先将异常传播到其父级!然后,父级将 1)取消其其余子级,2) 取消自身,以及 3) 将异常传播到其父级。异常将到达层次结构的根部,并且 CoroutineScope 启动的所有协程也将被取消。
notion image
 

CoroutineExceptionHandler

其是用于在协程中全局捕获异常行为的最后一种机制,你可以理解为,类似 Thread.uncaughtExceptionHandler 一样。
但需要注意的是,CoroutineExceptionHandler 仅在未捕获的异常上调用,也即这个异常没有任何方式处理时(比如在源头tryCatch了),由于协程是结构化的,当子协程发生异常时,它会优先将异常委托给父协程区处理,以此类推 直到根协程作用域或者顶级协程 。因此其永远不会使用我们子协程 CoroutineContext 传递的 CoroutineExceptionHandler(SupervisorJob 除外),对于 async 这种,而是直接向用户直接暴漏该异常,所以我们在具体调用处直接处理就行。
 
如果异常没有被处理,而且顶级协程 CoroutineContext 中没有携带 CoroutineExceptionHandler ,则异常会传递给默认线程的 ExceptionHandler 。在 Android 中,如果没有设置 Thread.setDefaultUncaughtExceptionHandler , 这个异常将立即被抛出,从而导致引发App崩溃。
 

Launch vs Async

未捕获的异常总是会被抛出。然而,不同的协程构建器以不同的方式处理异常。
  • Launch
    • 启动后,异常一发生就会被抛出。因此,您可以将可以抛出异常的代码包装在 try/catch 中,如下例所示
  • Async
    • 当 async 用作根协程(协程是 CoroutineScope 实例或supervisorScope 的直接子级)时,不会自动抛出异常,而是在调用 .await() 时抛出异常。
      要处理异步抛出的异常(只要它是根协程),您可以将 .await() 调用包装在 try/catch 中:

SupervisorJob

使用 SupervisorJob,一个孩子的失败不会影响其他孩子。 SupervisorJob 不会取消自身或其其余子级。此外,SupervisorJob 也不会传播异常,而是让子协程处理它。
 
与 SupervisorJob 不同,Job 会自动在层次结构中向上传播,因此下面的示例不会调用 catch 块:
 

参考文档

  • 协程
  • Kotlin 协程挂起函数原理Kotlin 协程 Job
    • Utterance
    Catalog