协程的异常处理

invokeOnCompletion监听协程状态

在了解协程的异常处理之前,先来熟悉下协程之间job是如何绑定关系,还是通过一个例子来熟悉他们之间的关系:

1
2
3
4
5
6
7
8
private fun demo(){
    val job = GlobalScope.launch {
        Log.d(TAG, "demo: launch代码")
    }
    job.invokeOnCompletion {
        Log.d(TAG, "demo: invokeOnCompletion")
    }
}

执行结果:

1
2
CoroutineJobActivity    com.example.coroutinescopedemo       D  demo: launch代码
CoroutineJobActivity    com.example.coroutinescopedemo       D  demo: invokeOnCompletion

可以看出来invokeOnCompletion是job执行完毕的回调。

通过launch创建协程内部会构造一个StandaloneCoroutine,它就是一个job,并且launch返回的就是该job: launch执行代码.png StandaloneCoroutine继承自AbstractCoroutine: AbstractCoroutine构造器执行代码.png

initParentJob.png 因此parentHandle为NonDisposableHandle,然后方法结束。 此时执行完launch后,继续执行了job的invokeOnCompletion方法,它是一个抽象方法,实现方法在jobSupport类: JobSupport下的invokeOnCompletion方法.png 三个参数的invokeOnCompletion方法.png

我们看下makeNode方法实现: makeNode的实现.png 从调用链上看onCancelling传进来的是false,并且此时的handler还不是一个JobNode节点,所以会创建了一个InvokeOnCompletion对象,并把外面传进来的ComppletionHandler传到InvokeOnCompletion中,看下InvokeOnCompletion对象:

1
2
3
4
5
private class InvokeOnCompletion(
    private val handler: CompletionHandler
) : JobNode()  {
    override fun invoke(cause: Throwable?) = handler.invoke(cause)
}

InvokeOnCompletion是一个JobNode节点:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
internal abstract class JobNode : CompletionHandlerBase(), DisposableHandle, Incomplete {
    /**
     * Initialized by [JobSupport.makeNode].
     */
    lateinit var job: JobSupport
    override val isActive: Boolean get() = true
    override val list: NodeList? get() = null
    override fun dispose() = job.removeNode(this)
    override fun toString() = "$classSimpleName@$hexAddress[job@${job.hexAddress}]"
}

JobNode实现了CompletionHandlerBase抽象类:

1
2
3
internal actual abstract class CompletionHandlerBase actual constructor() : LockFreeLinkedListNode(), CompletionHandler {
    actual abstract override fun invoke(cause: Throwable?)
}

它也是实现了CompletionHandler接口,CompletionHandler是一个闭包,从上面继承以及JobNode实现来看,在它的invoke实现里面,会触发传进来的CompletionHandler的invoke实现,从实现来看其实这就是个静态代理的模式。

结论: makeNode内部会返回一个InvokeOnCompletion对象,它是一个JobNode节点。

回到上面的invokeOnComletion的实现,创建了InvokeOnCompletion后,会走到loopOnState的函数,它是遍历state,它的初始状态是一个Empty状态,并且state.isActive=true。所以最终把刚才创建的InvokeOnCompletion设置到了state上,并且返回了该InvokeOnCompletion。

结论: 当调用了job的invokeOnCompletion方法后,会把外界创建好的CompletionHandler传给了InvokeCompletion,InvokeCompletion是一个JobNode,在它的invoke实现中,会回调到外界的CompletionHandler中。并且把创建的InvokeCompletion设置到当前job的state上了。

协程体执行完job的执行: 在之前分析协程体其实是一个SuspendLambda,在它的invokeSuspend调用完后,会执行它的continuation的resumeWith方法,在上面例子中其实是StandaloneCoroutine,它是一个job,所以看下它的resumeWith实现:

1
2
3
4
5
public final override fun resumeWith(result: Result<T>) {
    val state = makeCompletingOnce(result.toState())
    if (state === COMPLETING_WAITING_CHILDREN) return
    afterResume(state)
}

将协程体的结果传到了makeCompletingOnce方法中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
internal fun makeCompletingOnce(proposedUpdate: Any?): Any? {
    loopOnState { state ->
        val finalState = tryMakeCompleting(state, proposedUpdate)
        when {
            finalState === COMPLETING_ALREADY ->
                throw IllegalStateException(
                    "Job $this is already complete or completing, " +
                        "but is being completed with $proposedUpdate", proposedUpdate.exceptionOrNull
                )
            finalState === COMPLETING_RETRY -> return@loopOnState
            else -> return finalState // COMPLETING_WAITING_CHILDREN or final state
        }
    }
}

轮训获取state,并调用了tryMakeCompleting方法: tryMakeCompleting实现.png 从上面的分析知道state它是一个JobNode,它是一个InvokeCompletion,它不是一个ChildHandleNode,并且proposedUpdate不是CompletedExceptionally,所以会执行tryFinalizeSimpleState: tryFinalizeSimpleState实现.png 内部调用了completeStateFinalization方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private fun completeStateFinalization(state: Incomplete, update: Any?) {
    /*
     * Now the job in THE FINAL state. We need to properly handle the resulting state.
     * Order of various invocations here is important.
     *
     * 1) Unregister from parent job.
     */
    parentHandle?.let {
        it.dispose() // volatile read parentHandle _after_ state was updated
        parentHandle = NonDisposableHandle // release it just in case, to aid GC
    }
    val cause = (update as? CompletedExceptionally)?.cause
    /*
     * 2) Invoke completion handlers: .join(), callbacks etc.
     *    It's important to invoke them only AFTER exception handling and everything else, see #208
     */
    if (state is JobNode) { // SINGLE/SINGLE+ state -- one completion handler (common case)
        try {
            state.invoke(cause)
        } catch (ex: Throwable) {
            handleOnCompletionException(CompletionHandlerException("Exception in completion handler $state for $this", ex))
        }
    } else {
        state.list?.notifyCompletion(cause)
    }
}

该方法中判断state是否是JobNode,如果是的话,会调用invoke方法。在上面分析过state是一个InvokeOnCompletion对象,在它的invoke里面会回调到传进来的CompletionHandler的invoke中,所以在上面例子中打印了job执行完成的日志。

invokeOnCompletion监听job的异常

还是通过一个例子来分析下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
private fun demo3(){
    val exceptionHandler =CoroutineExceptionHandler{ coroutineContext, throwable ->
        Log.d(TAG, "demo3: CoroutineExceptionHandler--异常信息:${throwable?.message}")
    }
    val job = GlobalScope.launch(exceptionHandler) {
        Log.d(TAG, "demo3: launch代码")
        throw RuntimeException("协程体里面发生异常了")
    }
    job.invokeOnCompletion {
        Log.d(TAG, "demo3: invokeOnCompletion--异常信息:${it?.message}")
    }
}

此处用了一个CoroutineExceptionHandler方法,来创建CoroutineExceptionHandler对象来拦截异常的,这块先不用管,我们看下日志:

1
2
3
com.example.coroutinescopedemo       D  demo3: launch代码
com.example.coroutinescopedemo       D  demo3: CoroutineExceptionHandler--异常信息:协程体里面发生异常了
com.example.coroutinescopedemo       D  demo3: invokeOnCompletion--异常信息:协程体里面发生异常了

可以看出来,invokeOnCompletion中也能收到异常的消息。 但是异常和正常收数据可能不太一样,主要区别是在resumeWith之后的处理不太一样: resumeWith之后异常的流程.png 如果是非异常的话,会走tryFinalizeSimpleState;如果是异常的时候会走tryMakeCompletingSlowPath,在该方法里面会触发finalizeFinishingState方法,该方法有个很重要逻辑: finalizeFinishingState主要逻辑.png 由于cancelParent返回false,所以会触发handleJobException,StandaloneCoroutine重写了该方法: StandaloneCoroutine重写handleJobException方法.png

handleCoroutineException: CoroutineExceptionHandler的handleCoroutineException方法.png

如果context中CoroutineExceptionHandler不为空,则异常不会往上抛了,所以此例中定义了context的CoroutineExceptionHandler程序不会崩溃。 回到上面再来看invokeOnCompletion的CompletionHandler触发时机,它是在finalizeFinishingState中触发,接着会调用到completeStateFinalization,最终在里面触发了invokeOnCompletion传进来的CompletionHandler。

协程异常try住:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
private fun demo4() {
    val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
        Log.d(TAG, "demo4: CoroutineExceptionHandler--异常信息:${throwable?.message}")
    }
    val job = GlobalScope.launch(exceptionHandler) {
        try {
            Log.d(TAG, "demo4: launch代码")
            throw RuntimeException("协程体里面发生异常了")
        } catch (e: Exception) {
            Log.d(TAG, "demo4: catch中代码")
        }
    }
    job.invokeOnCompletion {
        Log.d(TAG, "demo4: invokeOnCompletion--异常信息:${it?.message}")
    }
}

日志如下:

1
2
3
com.example.coroutinescopedemo       D  demo4: launch代码
com.example.coroutinescopedemo       D  demo4: catch中代码
com.example.coroutinescopedemo       D  demo4: invokeOnCompletion--异常信息:null

从日志中看,此处的CoroutineExceptionHandler不回调,并且invokeOnCompletion收不到异常信息。 我们通过字节码可以看到原因: 协程中try后的字节码.png 此处的异常catch住后,给到JobSupport就不是异常了,所以按照成功的处理方式一样。

Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy