本章我们简单探讨一下异常处理:

我们知道协程传入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
}

我们看到SupervisorJobImplchildCancelled返回false让其有机会调用自己处理器,且错误不会向更外层传递。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐