为何协程异常未被父CoroutineScope的异常处理器捕获?
我对CoroutineExceptionHandler进行了测试,以下是测试代码:
fun createExceptionHandler(name: String) = CoroutineExceptionHandler { context, throwable -> println("[${Thread.currentThread().name} - $name] Caught $throwable\n${context[CoroutineExceptionHandler]}") } @Test fun coroutineScope_exceptionHandle() = runBlocking<Unit> { val coroutineScope = CoroutineScope(Job() + createExceptionHandler("supervisor")) coroutineScope.launch(createExceptionHandler("launch1")) { launch(CoroutineName("launch2") + createExceptionHandler("launch2")) { throw Exception("[${Thread.currentThread().name}] Error !") } launch(CoroutineName("launch3")) { println("[${Thread.currentThread().name}] launch3") } } delay(1000L) }
运行结果如下:
[DefaultDispatcher-worker-3 @launch3#6] launch3 [DefaultDispatcher-worker-2 @launch2#5 - launch1] Caught java.lang.Exception: [DefaultDispatcher-worker-2 @launch2#5] Error ! ...
我原本期望该异常由名为supervisor的CoroutineScope异常处理器处理,想请教为何最终是launch1的异常处理器捕获了该异常?
问题解答
这是因为协程异常传播遵循**从子协程向父协程传递,直到找到第一个能处理异常的CoroutineExceptionHandler**的规则,具体原因如下:
子协程
launch2的异常处理器无效:只有顶级协程(直接由CoroutineScope启动的协程)或者根协程(无父Job的协程)的CoroutineExceptionHandler才会生效。launch2是launch1的子协程,它的异常处理器会被忽略,异常直接向上传递给父协程launch1。父协程
launch1的异常处理器优先捕获:launch1是由CoroutineScope直接启动的根协程,它的CoroutineExceptionHandler有效。当launch2的异常传递到launch1时,launch1的处理器会立即捕获并处理异常,不会继续向上传递给CoroutineScope的处理器。CoroutineScope的处理器仅处理未被上层捕获的异常:只有当所有子协程、根协程都未处理异常时,CoroutineScope自身的CoroutineExceptionHandler才会触发。你的代码中launch1已经处理了异常,所以supervisor处理器不会被调用。
如果想让supervisor的处理器捕获异常,可选择:
- 去掉
launch1的异常处理器,让异常继续向上传递到CoroutineScope; - 使用
SupervisorJob替代普通Job(此操作仅改变协程失败的传播行为,子协程失败不影响其他兄弟协程,异常仍会传递到上层有有效处理器的位置)。
内容的提问来源于stack exchange,提问作者EARTH H

