Kotlin协程核心库分析-5 Job异常处理器注意点
开工
本章我们简单探讨一下异常处理:
我们知道协程传入CoroutineExceptionHandler对象即可捕获异常,那么对于子协程是否适用呢?
fun main() {
val eChild = CoroutineExceptionHandler { coroutineContext, throwable ->
println("子协异常处理器" + throwable.localizedMessage)
}
val eParent = CoroutineExceptionHandler { coroutineContext, throwable ->
println("父协程" + throwable.localizedMessage)
}
//创建一个Job
val job = GlobalScope.launch(eParent) {
launch(eChild) {
val d = 1 / 0
}
//为了让子协程完成
delay(100)
println("协程完成")
}
TimeUnit.SECONDS.sleep(1)
println("结束")
}
我们预期的行为是:eChild捕获异常,然后父协程因为异常取消.
我们预期的输出:
子协异常处理器 / by zero
结束
实际输出
父协程/ by zero
结束
可见子协程的异常处理器并未生效,我们来看看为何失效又如何让其生效.
我们的异常会调用到如下函数中:
//JobSupport.kt
private fun finalizeFinishingState(state: Finishing, proposedUpdate: Any?): Any? {
//因为异常不为空会走到这
if (finalException != null) {
//核心代码就一行
val handled = cancelParent(finalException) || handleJobException(finalException)
if (handled) (finalState as CompletedExceptionally).makeHandled()
}
}
我们注意看如下两行
//因为异常不为空会走到这
if (finalException != null) {
//核心代码就一行
val handled = cancelParent(finalException) || handleJobException(finalException)
}
我没看到cancelParent(finalException)函数为false时会调用右侧handleJobException(finalException)
我们先了解下handleJobException实现:
private open class StandaloneCoroutine(
parentContext: CoroutineContext,
active: Boolean
) : AbstractCoroutine<Unit>(parentContext, active) {
override fun handleJobException(exception: Throwable): Boolean {
handleCoroutineException(context, exception)
return true
}
}
public fun handleCoroutineException(context: CoroutineContext, exception: Throwable) {
try {
//获取异常处理器然后调用
context[CoroutineExceptionHandler]?.let {
it.handleException(context, exception)
return
}
} catch (t: Throwable) {
handleCoroutineExceptionImpl(context, handlerException(exception, t))
return
}
handleCoroutineExceptionImpl(context, exception)
}
可见handleJobException(finalException)函数就是用来调用我们的异常处理器.
后过头
//因为异常不为空会走到这
if (finalException != null) {
//核心代码就一行
val handled = cancelParent(finalException) || handleJobException(finalException)
}
handleJobException执行必须是cancelParent(finalException)返回false时才会触发.cancelParent什么时候返回false,什么是时候返回true ?这个在上一篇已经讲述过Kotlin协程核心库分析-4 Job父子取消
private fun cancelParent(cause: Throwable): Boolean {
if (isScopedCoroutine) return true
val isCancellation = cause is CancellationException
val parent = parentHandle
if (parent === null || parent === NonDisposableHandle) {
return isCancellation
}
//最后交给了childCancelled
return parent.childCancelled(cause) || isCancellation
}
关于childCancelled本人其它文章有讲解过,我们看其中的一个实现
//JobSupport.kt
public open fun childCancelled(cause: Throwable): Boolean {
if (cause is CancellationException) return true
return cancelImpl(cause) && handlesException
}
如果父协程处理异常那么返回true 否则返回false,换句话说返回true的时候父协程会取消自己,false不会取消.
综上所诉在一般情况下如果异常会使父协程取消那么不会让子协程的异常处理器不会回调.
那么我们如果我们想让子协程自己处理异常呢?
fun main() {
val eChild = CoroutineExceptionHandler { coroutineContext, throwable ->
println("子协异常处理器" + throwable.localizedMessage)
}
val eParent = CoroutineExceptionHandler { coroutineContext, throwable ->
println("父协程" + throwable.localizedMessage)
}
//创建一个Job
val job = GlobalScope.launch(eParent) {
launch(SupervisorJob()+eChild) {
delay(20)
val d = 1 / 0
}
//为了让子协程完成
delay(100)
println("协程完成")
}
TimeUnit.HOURS.sleep(1)
println("结束")
}
输出:
子协异常处理器/ by zero
协程完成
为何会这样呢?我们传入的SupervisorJob()会作为子协程的父亲,SupervisorJob继承SupervisorJobImpl.
而决定子协程异常处理器是否回调我们说过主要看childCancelled返回值.
private class SupervisorJobImpl(parent: Job?) : JobImpl(parent) {
override fun childCancelled(cause: Throwable): Boolean = false
}
我们看到SupervisorJobImpl的childCancelled返回false让其有机会调用自己处理器,且错误不会向更外层传递。
更多推荐



所有评论(0)