Kotlin 协程异常机制
异常的传播
由于 CoroutineScope 可以创建协程,并且您可以在协程内创建更多协程,因此会创建隐式任务层次结构。在任务层次结构中,每个协程都有一个父级,可以是 CoroutineScope 或另一个协程。新生成的协程的 CoroutineContext 与父级的 CoroutineContext 有继承关系。每个任务 Job 都有自己的生命周期,子任务的生命周期受父任务的生命周期控制,比如如果父级 job 关闭,子协程 job 也会被取消。
// jobA.cancel() 不影响 jobB 的执行,而 scope.cancel() jobA,jobB 都会被取消。 val scope = CoroutineScope(Job()) val jobA = scope.launch(CoroutineName("A")) { println("Name:${this.coroutineContext[CoroutineName]} Job:${this.coroutineContext[Job]}#${this.coroutineContext[Job]?.parent?.javaClass?.name} start") delay(100) println("${this.coroutineContext[CoroutineName]} end") } val jobB = scope.launch(CoroutineName("B")) { println("Name:${this.coroutineContext[CoroutineName]} Job:${this.coroutineContext[Job]}#${this.coroutineContext[Job]?.parent?.javaClass?.name} start") delay(500) println("${this.coroutineContext[CoroutineName]} end") }// jobA.cancel()// scope.cancel() // 保证执行完后再退出 Thread.sleep(5000) println("......")当协程因异常而失败时,它会优先将异常传播到其父级!然后,父级将 1)取消其其余子级,2) 取消自身,以及 3) 将异常传播到其父级。异常将到达层次结构的根部,并且 CoroutineScope 启动的所有协程也将被取消。

CoroutineExceptionHandler
其是用于在协程中全局捕获异常行为的最后一种机制,你可以理解为,类似 Thread.uncaughtExceptionHandler 一样。
但需要注意的是,CoroutineExceptionHandler 仅在未捕获的异常上调用,也即这个异常没有任何方式处理时(比如在源头tryCatch了),由于协程是结构化的,当子协程发生异常时,它会优先将异常委托给父协程区处理,以此类推 直到根协程作用域或者顶级协程 。因此其永远不会使用我们子协程 CoroutineContext 传递的 CoroutineExceptionHandler(SupervisorJob 除外),对于 async 这种,而是直接向用户直接暴漏该异常,所以我们在具体调用处直接处理就行。
// 2. 设置 CoroutineExceptionHandler 给 A 父级协程 val handler = CoroutineExceptionHandler { _, exception -> println("CoroutineExceptionHandler got $exception") } // 3. 传递给默认线程的 ExceptionHandler Thread.setDefaultUncaughtExceptionHandler(object : Thread.UncaughtExceptionHandler { override fun uncaughtException(t: Thread, e: Throwable) { println("t.name=${t.name}" + " error:" + e.toString()) } }) val scope = CoroutineScope(Job()) val jobA = scope.launch(CoroutineName("A")) { println("Name:${this.coroutineContext[CoroutineName]} Job:${this.coroutineContext[Job]}#${this.coroutineContext[Job]?.parent?.javaClass?.name} start") delay(100) // 1. 主动处理异常 // try { // throw NullPointerException() // } catch (e: Exception) { // e.printStackTrace() // } throw NullPointerException() println("${this.coroutineContext[CoroutineName]} end") } val jobB = scope.launch(CoroutineName("B")) { println("Name:${this.coroutineContext[CoroutineName]} Job:${this.coroutineContext[Job]}#${this.coroutineContext[Job]?.parent?.javaClass?.name} start") delay(500) println("${this.coroutineContext[CoroutineName]} end") } // 保证执行完后再退出 Thread.sleep(5000) println("......")如果异常没有被处理,而且顶级协程 CoroutineContext 中没有携带 CoroutineExceptionHandler ,则异常会传递给默认线程的 ExceptionHandler 。在 Android 中,如果没有设置 Thread.setDefaultUncaughtExceptionHandler , 这个异常将立即被抛出,从而导致引发App崩溃。
Launch vs Async
未捕获的异常总是会被抛出。然而,不同的协程构建器以不同的方式处理异常。
- Launch
启动后,异常一发生就会被抛出。因此,您可以将可以抛出异常的代码包装在 try/catch 中,如下例所示
scope.launch { try { codeThatCanThrowExceptions() } catch(e: Exception) { // Handle exception }}- Async
当 async 用作根协程(协程是 CoroutineScope 实例或supervisorScope 的直接子级)时,不会自动抛出异常,而是在调用 .await() 时抛出异常。 要处理异步抛出的异常(只要它是根协程),您可以将 .await() 调用包装在 try/catch 中:
supervisorScope { val deferred = async { codeThatCanThrowExceptions() } try { deferred.await() } catch(e: Exception) { // Handle exception thrown in async }}SupervisorJob
使用 SupervisorJob,一个孩子的失败不会影响其他孩子。 SupervisorJob 不会取消自身或其其余子级。此外,SupervisorJob 也不会传播异常,而是让子协程处理它。
// 使用 SupervisorJob,A 协程异常不会影响到 B 协程 val scope = CoroutineScope(SupervisorJob()) val jobA = scope.launch(CoroutineName("A")) { println("A start") delay(100) throw NullPointerException() println("A end") } val jobB = scope.launch(CoroutineName("B")) { println("B start") delay(500) println("B end") } // 保证执行完后再退出 Thread.sleep(5000) println("......")与 SupervisorJob 不同,Job 会自动在层次结构中向上传播,因此下面的示例不会调用 catch 块:
// job 在层次结构中优先向上传播coroutineScope { try { val deferred = async { codeThatCanThrowExceptions() } deferred.await() } catch(e: Exception) { // Exception thrown in async WILL NOT be caught here // but propagated up to the scope }}参考文档
https://book.kotlincn.net/text/exception-handling.html
https://medium.com/androiddevelopers/exceptions-in-coroutines-ce8da1ec060c