Kotlin之源码解析协程的取消和异常流程

前言:

前两篇文章我们介绍了协程的基础知识和上下文:

第一节:kotlin之协程基础知识

第二节:kotlin之协程的上下文

这篇文章我们将介绍协程的取消和异常。

1.协程的取消

前面我们介绍到launch()函数返回的是一个Job对象。想要取消一个正在运行的协程也比较简单,我们只需要调用Job的cancel()方法就可以取消一个协程,如下代码示例:

fun main() {
    val job = CoroutineScope(Dispatchers.IO).launch {
        repeat(3) {
            println("repeat: $it, isActive = $isActive")
        }
    }
    job.cancel()
    runBlocking { delay(100) }
}
// 输出
repeat: 0, isActive = false
repeat: 1, isActive = false
repeat: 2, isActive = false

从输出结果来看,cancel()方法并没有将协程正确的取消。这里在runBlocking()函数中delay100毫秒来保证JVM的存活。Job中的isActive字段已经是false了,但是repeat()函数中的代码还是成功执行了。cancel()方法只是更改了Job的状态,而并不能终止正在运行的协程。这就好比线程中的interrputed()方法。
就像官网介绍的那样,协程的取消是协作的,我们需要判断协程的状态来完成协程所谓真正意义上的取消。当我们在launch()函数中添加isActive字段时,我们的取消才是有意义的。如下代码示例:

fun main() {
    val job = CoroutineScope(Dispatchers.IO).launch {
        repeat(3) {
            if (!isActive) return@launch
            println("repeat: $it, isActive = $isActive")
        }
    }
    job.cancel()
    runBlocking { delay(100) }
}
// 无任何输出

2.父协程和子协程

默认情况下,在一个协程体内直接创建协程,二者会产生父子关系

// 父Job
CoroutineScope(Dispatchers.Default).launch {
    // 子job1    
    launch {  }
    // 子job2
    launch {  }
}

launch()函数:

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

StandaloneCoroutine也是一个Job,其继承关系如下:

--CoroutineContext
  --Element
    --Job
      --JobSupport
        --AbstractCoroutine
          --StandaloneCoroutine

在抽象类AbstractCoroutine的init块中会调用其父类JobSupport中的initParentJob()方法:

public abstract class AbstractCoroutine<in T>(
    parentContext: CoroutineContext,
    initParentJob: Boolean,
    active: Boolean
) : JobSupport(active), Job, Continuation<T>, CoroutineScope {
    ...
    init {
        if (initParentJob) initParentJob(parentContext[Job])
    }
    ...
}    
protected fun initParentJob(parent: Job?) {
    assert { parentHandle == null }
    // 根Job,没有parent 将NonDisposableHandle赋值给parentHandle
    if (parent == null) {
        parentHandle = NonDisposableHandle
        return
    }
    parent.start() // 确保父Job已启动
    val handle = parent.attachChild(this)
    // 将attachChild方法的返回值赋值给parentHandle
    parentHandle = handle
    // 现在检查我们的状态after_registering(请参阅tryFinalizeSimpleState操作顺序)
    if (isCompleted) {
        handle.dispose()
        parentHandle = NonDisposableHandle // 释放它以防万一,以帮助GC
    }
}

attachChild()函数:
public final override fun attachChild(child: ChildJob): ChildHandle {
return invokeOnCompletion(
onCancelling = true,
handler = ChildHandleNode(child).asHandler) as ChildHandle
}

invokeOnCompletion()方法返回的是一个ChildHandle,由于这里涉及到类的继承关系有点复杂,为了方便阅读,这里把JobNode中的类继承关系整理了一下:
在这里插入图片描述

invokeOnComletion()方法主要是将子Job添加到父Job的节点中,还会将Job的状态更改为Incomplete。这里可以理解为Job处于活跃状态。(注意:LockFreeLinkedListNode是一个双向链表的数据结构)

public final override fun invokeOnCompletion(
    onCancelling: Boolean,
    invokeImmediately: Boolean,
    handler: CompletionHandler
): DisposableHandle {
    // 根据hander类型和onCancelling创建JobNode对象
    val node: JobNode = makeNode(handler, onCancelling)
    loopOnState { state ->
        when (state) {
            is Empty -> { // EMPTY_X状态--没有完成处理程序
                if (state.isActive) {
                    // 将Job的状态更改为 InComplete
                    if (_state.compareAndSet(state, node)) return node
                } else
                    promoteEmptyToNodeList(state) // 这样我们就可以为非活动协同程序添加监听器
            }
            is Incomplete -> {
                val list = state.list
                if (list == null) { // 如果list为空,就创建一个list,并更改当前job的状态
                    promoteSingleToNodeList(state as JobNode)
                } else {
                    ...
                    // 将子节点添加到父节点中
                    if (addLastAtomic(state, list, node)) return node
                    ...
                }
            }
            else -> { // is complete
                // 父Job已经处于完成状态了,取消他的子Job
                if (invokeImmediately) handler.invokeIt((state as? CompletedExceptionally)?.cause)
                return NonDisposableHandle
            }
        }
    }
}

首先我们来看下makeNode()方法:

private fun makeNode(handler: CompletionHandler, onCancelling: Boolean): JobNode {
    val node = if (onCancelling) {
        (handler as? JobCancellingNode)
            ?: InvokeOnCancelling(handler)
    } else {
        (handler as? JobNode)
            ?.also { assert { it !is JobCancellingNode } }
            ?: InvokeOnCompletion(handler)
    }
    node.job = this
    return node
}

这里我们的onCancelling传的是true,handler是一个ChildHandleNode,同时也是JobCancellingNode的子类,所以这里我们的node变量就是一个JobCancellingNode类型,最后我们将当前的JobSupport赋值给node.job,然后返回node。(注意:这里的Job就是我们当前Job的父Job)。

internal abstract class JobNode : CompletionHandlerBase(), DisposableHandle, Incomplete {
    lateinit var job: JobSupport
    ...
}

internal abstract class JobCancellingNode : JobNode()

internal class ChildHandleNode(
    @JvmField val childJob: ChildJob
) : JobCancellingNode(), ChildHandle {
    override val parent: Job get() = job
    override fun invoke(cause: Throwable?) = childJob.parentCancelled(job)
    override fun childCancelled(cause: Throwable): Boolean = job.childCancelled(cause)
}

可以看到最终这里我们把job赋值了parent。当子Job取消的时候,我们使用childCancelled()方法来决定要不要取消父Job,当父Job取消的时候我们调用parentCancelled()方法来取消它的子Job。invoke方法由CompletionHandler覆盖而来,由上面的继承关系我们可以看到JobNode继承自CompletionHandlerBase

public typealias CompletionHandler = (cause: Throwable?) -> Unit

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

// CompletionHandler的扩展函数
internal actual inline fun CompletionHandler.invokeIt(cause: Throwable?) = invoke(cause)

loopOnState()方法中有一个while循环来查询我们Job的状态:

private inline fun loopOnState(block: (Any?) -> Unit): Nothing {
    while (true) {
        block(state)
    }
}

private val SEALED = Symbol("SEALED")
private val EMPTY_NEW = Empty(false)
private val EMPTY_ACTIVE = Empty(true)

// 由于这里的active是true,所以Job的默认状态是 EMPTY_ACTIVE
private val _state = atomic<Any?>(if (active) EMPTY_ACTIVE else EMPTY_NEW)

internal val state: Any? get() {
    _state.loop { state -> // 帮助程序循环状态(完成正在进行的原子操作)
        if (state !is OpDescriptor) return state
        state.perform(this)
    }
}

由此可以看出我们Job的初始状态是EMPTY_ACTIVE。

3.Job.cancel()

JobSupport实现了Job接口,并重写了Job中的cancel()方法:

public fun cancel(cause: CancellationException? = null)
public override fun cancel(cause: CancellationException?) {
    // 如果cause为空,则创建默认的异常
    cancelInternal(cause ?: defaultCancellationException())
}
internal inline fun defaultCancellationException(message: String? = null, cause: Throwable? = null) =
    // 如果message为空,则创建默认的message
    JobCancellationException(message ?: cancellationExceptionMessage(), cause, this)
public open fun cancelInternal(cause: Throwable) {
    cancelImpl(cause)
}

cancelImpl()函数:

// job内部调用cancel方法
public open fun cancelInternal(cause: Throwable) {
    cancelImpl(cause)
}

// 父Job取消,通知取消其子Job
public final override fun parentCancelled(parentJob: ParentJob) {
    cancelImpl(parentJob)
}

// 子Job取消,通知取消其父Job
public open fun childCancelled(cause: Throwable): Boolean {
    if (cause is CancellationException) return true
    return cancelImpl(cause) && handlesException
}
// 注意:这里的cause之所以声明为Any,是因为会传入不同类型的参数,如果由上面的
//      cancelInternal()、childCancelled()方法调用则是一个Throwable。
//      如果由parentCancelled调用则是ParentJob

internal fun cancelImpl(cause: Any?): Boolean 
    // finalState的默认状态为COMPLETING_ALREADY
    var finalState: Any? = COMPLETING_ALREADY
    
    // 根Job中onCancelComplete为true,详见JobImpl类
    if (onCancelComplete) {
        // 确保它正在完成,如果cancelMakeCompleting 返回状态,则表示它已完成并记录了异常
        finalState = cancelMakeCompleting(cause)
        // 等待子Job完成
        if (finalState === COMPLETING_WAITING_CHILDREN) return true
    }
    
    if (finalState === COMPLETING_ALREADY) {
        finalState = makeCancelling(cause)
    }
    return when {
        finalState === COMPLETING_ALREADY -> true
        finalState === COMPLETING_WAITING_CHILDREN -> true
        finalState === TOO_LATE_TO_CANCEL -> false
        else -> {
            afterCompletion(finalState)
            true
        }
    }
}

onCancelComplete是JubSupport中声明的一个可覆盖的布尔类型的属性:

internal open val onCancelComplete: Boolean get() = false

private class CompletableDeferredImpl<T>( parent: Job?) : JobSupport(true), CompletableDeferred<T> {
    ...
    override val onCancelComplete get() = true
    ...
}


internal open class JobImpl(parent: Job?) : JobSupport(true), CompletableJob {
    ...
    override val onCancelComplete get() = true
    ...
}    

而我们在创建协程上下文对象的时候所使用的Job都是JobImpl,所以当onCancelComplete为true的时候,我们可以理解为我们在取消一个根Job或某个子Job因为产生了JobCancellationException以外的异常来取消它的父Job。我们接着往下看cancelMakeCompleting()方法的实现:

// cause同上面的cancelImpl()方法
// 它包含一个循环,从不返回COMPLETING_RETRY,可以返回:
// COMPLETING_ALREADY -- 如果已经完成或正在完成
// COMPLETING_WAITING_CHILDREN -- 如果开始等待孩子
// final state -- 完成后调用afterCompletion

private fun cancelMakeCompleting(cause: Any?): Any? {
    loopOnState { state ->
        if (state !is Incomplete || state is Finishing && state.isCompleting) {
            // 已经完成,直接返回
            return COMPLETING_ALREADY
        }
        // 创建一个CompletedExceptionally对象,并且cause为JobCancellationException
        val proposedUpdate = CompletedExceptionally(createCauseException(cause))
        val finalState = tryMakeCompleting(state, proposedUpdate)
        if (finalState !== COMPLETING_RETRY) return finalState
    }
}
private fun createCauseException(cause: Any?): Throwable = when (cause) {
    is Throwable? -> cause ?: defaultCancellationException()
    // 如果cause是ParentJob,则调用getChildJobCancellationCause()
    else -> (cause as ParentJob).getChildJobCancellationCause()
}

override fun getChildJobCancellationCause(): CancellationException {
    // 确定此作业的根本取消原因(为什么要取消其子项?)
    val state = this.state
    val rootCause = when (state) {
        is Finishing -> state.rootCause
        is CompletedExceptionally -> state.cause
        is Incomplete -> error("Cannot be cancelling child in this state: $state")
        else -> null // 在正常完成时使用以下代码创建异常
    }
    return (rootCause as? CancellationException) ?: JobCancellationException("Parent job is ${stateString(state)}", rootCause, this)
}

接着会调用到tryMakeCompleting()方法,当onCancelComplete为false时,它会调用到makeCancelling()方法,这里面也会涉及到tryMakeCompleting()的方法的调用,包括协程的异常过程也会调用到该方法,下面我们先来看下Job的异常。

4.Job抛出异常

在BaseContinuatonImpl类中协程已经将异常捕获,并且会将异常作为结果值传递给resumeWith()方法:

internal abstract class BaseContinuationImpl(
    public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {
    public final override fun resumeWith(result: Result<Any?>) {
        var current = this
        var param = result
        while (true) {
            probeCoroutineResumed(current)
            with(current) {
                val completion = completion!! 
                val outcome: Result<Any?> =
                    try {
                        val outcome = invokeSuspend(param)
                        if (outcome === COROUTINE_SUSPENDED) return
                        Result.success(outcome)
                    } catch (exception: Throwable) {
                        Result.failure(exception)
                    }
                releaseIntercepted()
                if (completion is BaseContinuationImpl) {
                    current = completion
                    param = outcome
                } else {
                    completion.resumeWith(outcome)
                    return
                }
            }
        }
    }
 }  

然后调用AbstratCoroutine中的resumeWith()方法:

// 使用具有指定结果的协同程序完成此操作的执行。
public final override fun resumeWith(result: Result<T>) {
    val state = makeCompletingOnce(result.toState())
    if (state === COMPLETING_WAITING_CHILDREN) return
    afterResume(state)
}

protected open fun afterResume(state: Any?): Unit = afterCompletion(state)

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 // COMPLENG_WAITING_CHILDREN或最终状态
        }
    }
}

tryMakeCompleting()方法:

// 返回 COMPLETING 符号或最终状态之一:
// COMPLETING_ALREADY -- 当已经完成或完成时
// COMPLETING_RETRY -- 因干扰而需要重试时
// COMPLETING_WAITING_CHILDREN -- 完成并正在等待孩子
// final state -- 完成后,用于调用 afterCompletion

private fun tryMakeCompleting(state: Any?, proposedUpdate: Any?): Any? {
    if (state !is Incomplete)
        return COMPLETING_ALREADY
    /*
     * 快速路径--没有子级等待&&简单状态(无列表)&&不取消=>可以立即完成
     * 取消(失败)总是必须经过Finishing状态才能序列化异常处理。
     * 否则,可能会在(已完成状态->已处理的异常和新附加的子级/联接)之间发生竞争
     * 这可能会错过未处理的异常。
     */
    if ((state is Empty || state is JobNode) && state !is ChildHandleNode && proposedUpdate !is CompletedExceptionally) {
        if (tryFinalizeSimpleState(state, proposedUpdate)) {
            // Completed successfully on fast path -- return updated state
            return proposedUpdate
        }
        return COMPLETING_RETRY
    }
    // 单独的慢速路径功能可简化分析
    return tryMakeCompletingSlowPath(state, proposedUpdate)
}

tryMakeCompletingSlowPath()方法:

// 返回 COMPLETING 符号或最终状态之一
// COMPLETING_ALREADY -- 当已经完成或完成时
// COMPLETING_RETRY -- 因干扰而需要重试时
// COMPLETING_WAITING_CHILDREN -- 完成并正在等待孩子
// final state -- 完成后,用于调用 afterCompletion

private fun tryMakeCompletingSlowPath(state: Incomplete, proposedUpdate: Any?): Any? {
    // 获取状态的列表或提升到列表以正确操作子列表
    val list = getOrPromoteCancellingList(state) ?: return COMPLETING_RETRY
    // 如果我们还没有进入完成状态,则升级到完成状态
    // 这种提升必须是状态更改的原子w.r.t,这样还没有激活的协同程序
    // 原子过渡到完成状态
    val finishing = state as? Finishing ?: Finishing(list, false, null)
    // 必须将更新同步到完成状态
    var notifyRootCause: Throwable? = null
    synchronized(finishing) {
        // 检查此状态是否已完成
        if (finishing.isCompleting) return COMPLETING_ALREADY
        // 标记为完成
        finishing.isCompleting = true
        // 如果我们需要提升到finishing,那么在这里进行原子化操作。
        // 我们在保持锁定的同时尽可能早地完成。这样可以确保我们尽快cancelImpl
        // (如果其他人更快的话),我们会尽快同步这个完成锁上的所有线程。
        if (finishing !== state) {
            if (!_state.compareAndSet(state, finishing)) return COMPLETING_RETRY
        }
        ##重要不变:只有一个线程(设置为完成)可以超过这一点
        assert { !finishing.isSealed } // 无法密封
        // 将新提议的异常添加到完成状态
        val wasCancelling = finishing.isCancelling
        (proposedUpdate as? CompletedExceptionally)?.let { finishing.addExceptionLocked(it.cause) }
        // 如果只是取消-->必须处理取消通知
        notifyRootCause = finishing.rootCause.takeIf { !wasCancelling }
    }
    // 在这里处理取消通知--它取消了所有的孩子,我们开始等待他们(原文如此!!)
    notifyRootCause?.let { notifyCancelling(list, it) }
    // now wait for children
    val child = firstChild(state)
    if (child != null && tryWaitForChild(finishing, child, proposedUpdate))
        return COMPLETING_WAITING_CHILDREN
    // 否则——我们就没有孩子了(都已经取消了?)
    return finalizeFinishingState(finishing, proposedUpdate)
}

该方法主要的作用是将当前Job的状态置为Finishing,发出协程被取消的通知。然后判断子Job是否已经完成,如果没有完成就等待它的子Job完成,否则调用finalizeFinishingState()。

5.关键类Finishing解析

// 正在完成和取消状态,
// 所有更新都由synchronized(this)保护,读取是不稳定的
private class Finishing(
    override val list: NodeList,
    isCompleting: Boolean,
    rootCause: Throwable?
) : SynchronizedObject(), Incomplete {
    private val _isCompleting = atomic(isCompleting)
    var isCompleting: Boolean
        get() = _isCompleting.value
        set(value) { _isCompleting.value = value }

    private val _rootCause = atomic(rootCause)
    var rootCause: Throwable? // 注:即使在密封的情况下,根本原因也会保留
        get() = _rootCause.value
        set(value) { _rootCause.value = value }

    private val _exceptionsHolder = atomic<Any?>(null)
    private var exceptionsHolder: Any? // 包含null | Throwable | ArrayList | SEALED
        get() = _exceptionsHolder.value
        set(value) { _exceptionsHolder.value = value }

    // 注意:密封时无法修改
    val isSealed: Boolean get() = exceptionsHolder === SEALED
    val isCancelling: Boolean get() = rootCause != null
    override val isActive: Boolean get() = rootCause == null // !isCancelling

    // 密封当前状态并返回异常列表
    // 由“synchronized(this)”保护`
    fun sealLocked(proposedException: Throwable?): List<Throwable> {
        val list = when(val eh = exceptionsHolder) { // volatile read
            null -> allocateList()
            is Throwable -> allocateList().also { it.add(eh) }
            is ArrayList<*> -> eh as ArrayList<Throwable>
            else -> error("State is $eh") // 已经密封——不可能发生
        }
        val rootCause = this.rootCause // 原子读取
        rootCause?.let { list.add(0, it) } // 注意--根本原因从头开始
        if (proposedException != null && proposedException != rootCause) list.add(proposedException)
        exceptionsHolder = SEALED
        return list
    }

    // 由“synchronized(this)”保护`
    fun addExceptionLocked(exception: Throwable) {
        val rootCause = this.rootCause // 原子读取
        if (rootCause == null) {
            this.rootCause = exception
            return
        }
        if (exception === rootCause) return // 无需做任何操作
        when (val eh = exceptionsHolder) { // 原子读取
            null -> exceptionsHolder = exception
            is Throwable -> {
                if (exception === eh) return // 无需做任何操作
                exceptionsHolder = allocateList().apply {
                    add(eh)
                    add(exception)

                }
            }
            is ArrayList<*> -> (eh as ArrayList<Throwable>).add(exception)
            else -> error("State is $eh") // 已经密封——不可能发生
        }
    }

    private fun allocateList() = ArrayList<Throwable>(4)

    override fun toString(): String =
        "Finishing[cancelling=$isCancelling, completing=$isCompleting, rootCause=$rootCause, exceptions=$exceptionsHolder, list=$list]"
}

仔细一看其实代码量并不多,主要声明了isCompleting、rootCause、exceptionsHolder、isSealed、isCancelling属性,同时覆盖了isActive属性。两个比较重要的方法:

fun sealLocked(proposedException: Throwable?): List<Throwable> { }

fun addExceptionLocked(exception: Throwable) 

两者总是一前一后的调用,从而对异常进行保存以及状态的判断和流转。

private fun allocateList() = ArrayList<Throwable>(4)

allocateList()方法则是声明一个容量为4的ArrayList来存储Throwable。仔细分析这两个方法我们会发现,rootCase是在第一次调用addExceptionLocked()被赋值的,如果后面再次调用则是通过exceptionsHolder来保存的。因为父Job在等待子Job的时候,可能存在多个子Job都发生异常的情况,所以这里给保存异常的容量也做了一个升级变化。

  1. 发生一次直接用一个属性rootCase来保存
  2. 发生两次用两个属性rootCase和exceptionsHolder分别来保存
  3. 发生三次直接用ArrayList来保存

// 注意:这里是1、2、3是假设情况每个子Job发生的异常都是不同的情况

  1. 第一次调用addExceptionLocked():保存rootCause,exceptionsHolder此时为null
  2. 第二次调用addExceptionLocked():因为一个Job可能有多个子Job,父Job在等待子Job的时候可能会存在不止一个子Job发生JobCancellationException以外的异常,当异常不同于第一次调用时发生的异常,我们就将异常使用属性exceptionsHolder来保存
  3. 第三次调用addExceptionLocked():当这里的异常和rootCause、exceptionsHolder都不相等,那么索性创建一个ArrayList,把这第二次和第三次生的异常保存下来

从而这也对应了我们在接下来将在finalizeFinishingState()中调用finish.sealLocked()方法,总是取第一次发生的异常,然后将exceptionsHolder的状态置为SEALED。
关键代码块解析:

synchronized(finishing) {
    ...
    val wasCancelling = finishing.isCancelling
    (proposedUpdate as? CompletedExceptionally)?.let { finishing.addExceptionLocked(it.cause) }
    notifyRootCause = finishing.rootCause.takeIf { !wasCancelling }
}
notifyRootCause?.let { notifyCancelling(list, it) }
val isCancelling: Boolean get() = rootCause != null

finishing.isCancelling属性的get()方法是通过rootCause不为null来判断的,也是说在调用完一次finishing.addExceptionLocked()方法后,它就会返回true。这会影响到notifyRootCause的赋值,以及notifyCancelling()方法是否会执行。当多个子Job发生异常调用parentCancelled()方法来取消父Job的时候,notifyCancelling()方法不会被重复调用。

// finishing.rootCause.takeIf会返回 rootCause
notifyRootCause?.let { notifyCancelling(list, it) }

private fun notifyCancelling(list: NodeList, cause: Throwable) {
    // 先取消子Job
    onCancelling(cause)
    notifyHandlers<JobCancellingNode>(list, cause)
    // 然后取消父Job
    cancelParent(cause) // 暂时取消——如果没有父Job也没关系
}

private inline fun <reified T: JobNode> notifyHandlers(list: NodeList, cause: Throwable?) {
    var exception: Throwable? = null
    list.forEach<T> { node ->
        try {
            node.invoke(cause)
        } catch (ex: Throwable) {
            exception?.apply { addSuppressedThrowable(ex) } ?: run {
                exception =  CompletionHandlerException("Exception in completion handler $node for $this", ex)
            }
        }
    }
    exception?.let { handleOnCompletionException(it) }
}

到这里就比较清楚了,上面我们已经介绍了ChildHandleNode中的invoke方法:

internal class ChildHandleNode(
    @JvmField val childJob: ChildJob
) : JobCancellingNode(), ChildHandle {
    ...
    override fun invoke(cause: Throwable?) = childJob.parentCancelled(job)
    ...
}

父Job取消,使用for循环来取消所有的子Job,然后再逐级向上传递。是否取消其父Job主要受两个因素影响:

public open fun childCancelled(cause: Throwable): Boolean {
    if (cause is CancellationException) return true
    return cancelImpl(cause) && handlesException
}

一个因素是cause是CancellationException不会取消。另一个因素就是我们是否重写了childCancelled()方法,例如我们的常见的:顶层函数SupervisorJob()和 supervisorScope(),他们在其内部实现中所创建的Job都会重写childCancelled()方法,并将其置为false。这样当子Job抛异常时不会在去取消它的父Job。其代码如下:

public suspend fun <R> supervisorScope(block: suspend CoroutineScope.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return suspendCoroutineUninterceptedOrReturn { uCont ->
        val coroutine = SupervisorCoroutine(uCont.context, uCont)
        coroutine.startUndispatchedOrReturn(coroutine, block)
    }
}

private class SupervisorJobImpl(parent: Job?) : JobImpl(parent) {
    override fun childCancelled(cause: Throwable): Boolean = false
}

private class SupervisorCoroutine<in T>(
    context: CoroutineContext,
    uCont: Continuation<T>
) : ScopeCoroutine<T>(context, uCont) {
    override fun childCancelled(cause: Throwable): Boolean = false
}

接着就是调用finalizeFinishingState()方法,也是Job状态流转的最后一个方法了。

// 完成 Finishing -> Completed(终端状态)转换。
// 重要不变性:只有一个线程可以同时调用此方法。
// 返回创建并更新为的最终状态

private fun finalizeFinishingState(state: Finishing, proposedUpdate: Any?): Any? {
    //注意:建议的状态可以是Incomplete,例如
    // async{something.invokeOnCompletion{}
    // <-返回在引擎盖下实现Incomplete的句柄}
    assert { this.state === state } // 一致性检查--它不能更改
    assert { !state.isSealed } // 一致性检查--还不能密封
    assert { state.isCompleting } // 一致性检查--必须标记为完成
    val proposedException = (proposedUpdate as? CompletedExceptionally)?.cause
    // 创建最后一个异常并封存状态,以便不再添加异常
    val wasCancelling: Boolean
    val finalException = synchronized(state) {
        wasCancelling = state.isCancelling
        val exceptions = state.sealLocked(proposedException)
        val finalCause = getFinalRootCause(state, exceptions) //总是取第一个
        if (finalCause != null) addSuppressedExceptions(finalCause, exceptions)
        finalCause
    }
    // 创建最终状态对象
    val finalState = when {
        // 没有被取消(没有例外)->使用建议的更新值
        finalException == null -> proposedUpdate
        // 当我们可以在取消时按原样使用proposeUpdate对象时进行小型优化
        finalException === proposedException -> proposedUpdate
        // 取消的作业最终状态
        else -> CompletedExceptionally(finalException)
    }
    // 现在处理最后一个异常
    if (finalException != null) {
        val handled = cancelParent(finalException) || handleJobException(finalException)
        if (handled) (finalState as CompletedExceptionally).makeHandled()
    }
    // 处理作业状态之前的最终状态的状态更新
    // 以避免出现外部观察者可能看到作业处于最终状态,但尚未处理异常的情况。
    if (!wasCancelling) onCancelling(finalException)
    onCompletionInternal(finalState)
    // 然后CAS进入完成状态->必须成功
    val casSuccess = _state.compareAndSet(state, finalState.boxIncomplete())
    assert { casSuccess }
    // 并处理所有完成后的操作
    completeStateFinalization(state, finalState)
    return finalState
}

关键代码块:

if (finalException != null) { 
    // 默认取消父协程,如果异常不是JobCancellationException则分发异常
    val handled = cancelParent(finalException) || handleJobException(finalException) 
if (handled) (finalState as CompletedExceptionally).makeHandled() 
}

6.cancelParent方法()

/**
 * 取消作业时调用的方法,以可能将取消传播到父级。
 * 如果父级负责处理异常,则返回“true”,否则返回“false”。
 *
 * 不变量:对于[CancellationException]的实例,从不返回“false”,否则为此类异常
 * 可能泄漏到[CoroutineExceptionHandler]。
 */
private fun cancelParent(cause: Throwable): Boolean {
    // 是作用域的协程--不传播,将重新抛出
    if (isScopedCoroutine) return true

    /* CancellationException被认为是“正常”的,当子级生成时,父级通常不会被取消。
     * 这允许父级取消其子级(正常情况下)而不被取消,除非
     * child在其完成过程中崩溃并产生一些其他异常。
     */
    val isCancellation = cause is CancellationException
    val parent = parentHandle
    // 无父Job--忽略CancellationException,报告其他异常。
    if (parent === null || parent === NonDisposableHandle) {
        return isCancellation
    }

    // 取消父Job,但不要忘记检查取消
    return parent.childCancelled(cause) || isCancellation
}

如果这里的cause是CancellationException,cancelParent()方法就会直接返回true,所以不会走到handleJobException()方法,这也是我们常说CancellationException异常会被协程内部消化,不会当做异常来处理。
接着我们来看下这行代码:

// 是作用域的协程--不传播,将重新抛出
if (isScopedCoroutine) return true
/**
 * 对于作用域协同程序,返回“true”。
 * 作用域协同程序是在封闭作用域内按顺序执行的协同程序,没有任何并发性。
 * 作用域协同程序总是处理内部发生的任何异常——它们只是将其重新抛出到封闭作用域。
 * 作用域协程的示例有“coroutineScope”、“withTimeout”和“runBlocking”。
 */
protected open val isScopedCoroutine: Boolean get() = false

关于这个属性的定义注释写的也比较清楚,如挂起函数coroutineScope()、顶层函数runBlocking()其内部创建的Job都将该属性覆盖成了true。
在runBlocking()中:

public actual fun <T> runBlocking(context: CoroutineContext, block: suspend CoroutineScope.() -> T): T {
    ...
    val coroutine = BlockingCoroutine<T>(newContext, currentThread, eventLoop)
    coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
    return coroutine.joinBlocking()
}

private class BlockingCoroutine<T>(
    parentContext: CoroutineContext,
    private val blockedThread: Thread,
    private val eventLoop: EventLoop?
) : AbstractCoroutine<T>(parentContext, true, true) {

    override val isScopedCoroutine: Boolean get() = true
    ...
}

在coroutineScope()中:

public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R {
    ...
    return suspendCoroutineUninterceptedOrReturn { uCont ->
        val coroutine = ScopeCoroutine(uCont.context, uCont)
        coroutine.startUndispatchedOrReturn(coroutine, block)
    }
}

internal open class ScopeCoroutine<in T>(
    context: CoroutineContext,
    @JvmField val uCont: Continuation<T> // unintercepted continuation
) : AbstractCoroutine<T>(context, true, true), CoroutineStackFrame {
    ...
    final override val isScopedCoroutine: Boolean get() = true
    ...
}

BlockingCoroutine、ScopeCoroutine都是AbstractCoroutine的子类,而AbstractCoroutine又继承了JobSupport,两者都是不同类型的Job。所以使用这两种方式创建的Job。如果发生异常cancelParent()方法直接返回了true。这里你可能会感到疑惑,那么他们是如何处理异常的呢?其实在是isScopedCoroutine属性的备注中已经明确的告诉我们了。
是作用域的协程–不传播,将重新抛出。意思就是说我们将在后面的逻辑中处理该异常。

  • runBlocking()方法则是在BlockingCoroutine这个Job中的joinBlocking()方法来处理异常:
fun joinBlocking(): T {
    ...
    val state = this.state.unboxState()
    // 处理异常
    (state as? CompletedExceptionally)?.let { throw it.cause }
    return state as T
}
  • 而coroutineScope()方法是在ScopeCoroutine类中重写了afterResume()、afterCompletion()方法,他们会把结果值或异常交由外部的协程作用域处理:
override fun afterCompletion(state: Any?) {
    // 其他上下文恢复时,默认情况下以可取消的方式恢复
    uCont.intercepted().resumeCancellableWith(recoverResult(state, uCont))
}

override fun afterResume(state: Any?) {
    // 直接恢复,因为作用域已在正确的上下文中
    uCont.resumeWith(recoverResult(state, uCont))
}

7.handleJobException()方法

该方法是定义在JobSupport类中的一个可覆盖的方法:

// 处理父协同程序未处理的最终作业异常。如果它处理异常,则返回true(因此不需要在稍后阶段进行处理)。
// 它被设计为被没有表示异常的结果类型的类启动协同程序(StandaloneCoroutine和ActorCoroutine)覆盖。
// 当作业的最终异常被确定时,在它完成之前,该方法只调用一次。在调用时,作业及其所有子项都已完成。
protected open fun handleJobException(exception: Throwable): Boolean = false

并在其子类StandaloneCoroutine中进行了实现:

private open class StandaloneCoroutine(
    parentContext: CoroutineContext,
    active: Boolean
) : AbstractCoroutine<Unit>(parentContext, initParentJob = true, active = active) {
    override fun handleJobException(exception: Throwable): Boolean {
        handleCoroutineException(context, exception)
        return true
    }
}

上面我们介绍到当多个子Job发生异常的时候,它的父Job会用不同的方式将异常保存下来,但最终会使用第一个子Job抛出的异常来作为取消父Job的原因。handleCoroutineException()方法中的参数context,则是由parentContext和当前Job组合而成的上下文:

// 该协同程序的上下文,包括作为[Job]的该协同程序。
public final override val context: CoroutineContext = parentContext + this

// 这个范围的上下文与这个协同程序的上下文相同。
public override val coroutineContext: CoroutineContext get() = context

handleCoroutineException()方法:

public fun handleCoroutineException(context: CoroutineContext, exception: Throwable) {
    // 从上下文调用异常处理程序(如果存在)
    try {
        context[CoroutineExceptionHandler]?.let {
            it.handleException(context, exception)
            return
        }
    } catch (t: Throwable) {
        handleUncaughtCoroutineException(context, handlerException(exception, t))
        return
    }
    // 如果上下文中不存在处理程序,或者引发了异常,则回退到全局处理程序
    handleUncaughtCoroutineException(context, exception)
}

分析下这段代码,首先我们从当前上下文中去取CoroutineExceptionHandler这个类型的上下文对象,如果取到了,我们就处理异常,然后直接return。
CoroutineExceptionHandler是一个接口,它的具体实现如下:

public interface CoroutineExceptionHandler : CoroutineContext.Element {
    /**
     * 协同例程上下文中[CoroutineExceptionHandler]实例的键。
     */
    public companion object Key : CoroutineContext.Key<CoroutineExceptionHandler>

    /**
     * 在给定的[context]中处理未捕获的[exception]。它被调用
     * 如果协同程序有一个未捕获的异常。
     */
    public fun handleException(context: CoroutineContext, exception: Throwable)
}

如果上下文中不存在处理程序,或者引发了异常,则回退到全局处理程序。也就是说如果我们的上下文中没有定义CoroutineExceptionHandler的实现类,最终的异常还是交由系统来处理,这也是为什么我们在BaseContinuationImpl类中捕获了异常后,在没有添加CoroutineExceptionHandler上下文的情况下,应用仍会闪退的原因。

8.Job的收尾工作

if (finalException != null) {
    val handled = cancelParent(finalException) || handleJobException(finalException)
    if (handled) (finalState as CompletedExceptionally).makeHandled()
}

// 如果覆盖了onCancelling方法则通知子类,否则 do nothing
if (!wasCancelling) onCancelling(finalException)
// 如果覆盖了onCompletionInternal方法则通知子类,否则 do nothing
onCompletionInternal(finalState)
val casSuccess = _state.compareAndSet(state, finalState.boxIncomplete())
assert { casSuccess }
completeStateFinalization(state, finalState)
return finalState
  1. finalException != null,我们才去处理异常,然后流转job的状态。
  2. finalException == null正常流转Job的状态
  3. _state.compareAndSet(state, finalState.boxIncomplete())更改Job的状态
  4. completeStateFinalization(state, finalState)将子Job从父Job中移除
  5. 返回finalState

9.completeStateFinalization()方法

// 抑制==在构建最终完成原因时抑制任何异常时为true
private fun completeStateFinalization(state: Incomplete, update: Any?) {
    
     // 现在工作处于最终状态。我们需要正确处理由此产生的状态。
     // 这里的各种调用顺序很重要。
     // 1) 从父作业中注销。
    parentHandle?.let {
        it.dispose() // volatile read parentHandle_after_状态已更新
        parentHandle = NonDisposableHandle // 释放它以防万一,以帮助GC
    }
    val cause = (update as? CompletedExceptionally)?.cause
     // 2) 调用完成处理程序:.join()、回调等。
     // 只在异常处理和其他一切之后调用它们是很重要的,请参见#208
    if (state is JobNode) { // SINGLE/SINGLE+状态——一个完成处理程序(common case)
        try {
            state.invoke(cause)
        } catch (ex: Throwable) {
            handleOnCompletionException(CompletionHandlerException("Exception in completion handler $state for $this", ex))
        }
    } else {
        state.list?.notifyCompletion(cause)
    }
}

10.Job取消和异常的状态流转过程:

在这里插入图片描述

总结:

协程取消和异常的处理流程确实很复杂,只有很好的理解了它的工作原理,我们才能更好的在实际开发中去运用它。下面我们就通过对上面的分析来做出一些结论,如果什么不正确的地方,也请读者帮忙提出,本着学习的态度去改正它。

1.取消

  1. Job的取消是协作的,并不能取消协程的执行流程,只是对Job的状态进行流转,我们需要在必要的地方使用isActive来协作Job的取消
  2. 如果一个子Job调用cancel()方法来取消协程,那么它不会取消它的父Job(或者说父协程)
  3. 如果一个父Job调用cancel()方法,它会轮询取消它所有的子Job,并等待所有的子Job成功取消后,继续自行取消
  4. 调用Job.cancel()时,会创建一个JobCancellationException的异常,该异常会在Job的执行流程中被自行消化,并不会当做异常来处理,也就是不会走到方法handleJobException()中

2.异常

  1. 如果一个子Job抛异常了,那么默认情况下它会取消它的父Job,并且会通过childCancelled()
    方法逐级向上传递,直到根Job
  2. 如果一个父Job抛异常了,它会轮询取消它所有的子Job,并等待所有的子Job成功取消后,继续自行取消
  3. 如果我们创建协程上下文对象的时候携带了CoroutineExceptionHandler,那么我们的异常就会被捕获到自行处理,否则就交由系统处理,那么应用任会因异常而退出
  4. 如果我们使用顶层函数SupervisorJob()、supervisorScope()来创建一个Job,如果它的子Job发生异常则不会影响到的它的父Job,原因是重写了childCancelled()的方法
override fun childCancelled(cause: Throwable): Boolean = false
  1. 如果一个父Job的多个子Job都发生了异常,那么我们总是取第一个异常来作为父Job异常的原因
  2. 如果父Job发生异常了,那么它将不会在工作了,此时如果我们再调用launch()方法启动一个协程它也不会再工作了。这个结论是上面源码中没有分析,这是笔者在实际开发中遇到的问题,这里也分享给大家:
class MainActivity : AppCompatActivity() {
    
    private val tag = "MainActivity"
    private val scope =
        CoroutineScope(CoroutineExceptionHandler { _, e -> Log.e(tag, "${e.message}") })

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        findViewById<Button>(R.id.button).setOnClickListener {
            scope.launch {
                throw Exception("throw exception")
            }
        }
    }
    
}

第一次点击会打印:throw exception,第二次点击将不会有任何的反应,原因是我们在切换线程时会对父Job的状态进行判断如果它已经完成(或者处于异常状态)该协程将会直接使用异常作为结果值,直接恢复,并不会再执行协程体中的代码:

internal abstract class DispatchedTask<in T> internal constructor(
    public var resumeMode: Int
) : SchedulerTask() {
    ...
    final override fun run() {
       val job = if (exception == null && resumeMode.isCancellableMode) context[Job] else null
            if (job != null && !job.isActive) {
                // 直接携带异常恢复协程
                val cause = job.getCancellationException()
                cancelCompletedResult(state, cause)
                continuation.resumeWithStackTrace(cause)
            } else {
                if (exception != null) {
                    continuation.resumeWithException(exception)
                } else {
                    continuation.resume(getSuccessfulResult(state))
                }
            }
    }
    ...
}

到这里这篇关于协程取消和异常的文章总算是写完了,如果有什么写的不对的地方欢迎指出,大家一起学习交流。如果您觉得有帮助,麻烦点个赞再走,您的支持就是我创作的最大动力。

Kotlin 中,协程 Job 可能会被意外取消,通常是由于它们所在的上下文(如 Activity 的生命周期管理)发生变化导致。为了避免这种情况,你可以考虑以下几种策略: 1. **使用 try-catch-finally**: 在发起协程任务时,使用 try-catch 将 Job 包装起来,如果 Job 被取消,可以在 finally 子句中清理资源: ```kotlin try { GlobalScope.launch { // 你的协程任务 } } catch (cancellationException: CancellationException) { // 处理取消异常 } finally { cancelAndJoin() // 清理资源 } fun cancelAndJoin() { yourJob?.cancel() yourJob?.join() // 确保所有工作已完成后再关闭 } ``` 2. **避免不必要的全局 Job**: 尽量将 Job 定义在具体的范围,比如函数内部,这样在范围结束后 Job 自然会结束而不会引起意外取消。 3. **监听 Activity 或 Fragment 生命周期变化**: 对于 Android,当 Activity 要销毁时,记得取消正在运行的协程。可以使用 `onSaveInstanceState` 和 `onDestroyView` 进行监控: ```kotlin override fun onSaveInstanceState(outState: Bundle?) { super.onSaveInstanceState(outState) yourJob?.cancel() } override fun onDestroyView() { super.onDestroyView() yourJob?.cancel() } ``` 4. **使用 Job API**: 使用 Kotlin coroutines 的 `Job` 类和 `withContext` 函数,可以控制 Job 的生命周期,比如限制在一个特定的 Dispatcher 上运行: ```kotlin val cancellableJob = withContext(Dispatchers.IO) { GlobalScope.launch { // ... } } cancellableJob?.invokeOnCompletion { result -> // 结束后处理结果或清理资源 } ``` 通过以上方法,你可以更好地管理协程 Job,降低被意外取消的风险。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值