Kotlin-Coroutines

Kotlin-协程核心库分析-Job父子取消

2021-02-04  本文已影响0人  有腹肌的豌豆Z

父Job取消时如何取消子Job

fun main() {
    //创建一个Job,当然你也可以启动一个协程后返回
    val job = GlobalScope.launch {
        //启动一个子协程
        launch {
            Thread.sleep(200)
            println("子协程完成")
        }
        Thread.sleep(100)
        println("父协程完成")
    }

    job.cancel()
    TimeUnit.SECONDS.sleep(1)
    println("结束")

}

父协程完成
结束

我们看下子协程如何被取消的。
首先我们需要知道 子协程启动的时候会放一个监听器到父亲NodeList中. 这是在监听父协程么
也就是如下代码:

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

当我们的demo源码调用时:

最终会调用到:

  private fun makeCancelling(cause: Any?): Any? {
        var causeExceptionCache: Throwable? = null // lazily init result of createCauseException(cause)
        loopOnState { state ->
            when (state) {
                is Finishing -> {
                   //...由于协程未完成所以到这
                }
                is Incomplete -> {
                   //当前协程未完成,所判断走到这
                   //创建一个取消异常
                    val causeException = causeExceptionCache ?: createCauseException(cause).also { causeExceptionCache = it }
                    //当前协程是否存活 这里显然返回true
                    if (state.isActive) {
                       
                        if (tryMakeCancelling(state, causeException)) return COMPLETING_ALREADY
                    } else {
                     
                     //....
                        }
                    }
                }
                else -> return TOO_LATE_TO_CANCEL // already complete
            }
        }
    }

跟进tryMakeCancelling(state, causeException))


    private fun tryMakeCancelling(state: Incomplete, rootCause: Throwable): Boolean {
        //如果当前state不是NodeList,那么讲当前状态变为NodeList
        //在我们本案例当中当前state是ChildHandle所以要变为NodeList
        val list = getOrPromoteCancellingList(state) ?: return false
        //变为cancelling状态
        val cancelling = Finishing(list, false, rootCause)
        //切换状态
        if (!_state.compareAndSet(state, cancelling)) return false
        //唤醒NodeList各个节点
        notifyCancelling(list, rootCause)
        return true
    }

继续

private fun notifyCancelling(list: NodeList, cause: Throwable) {
        //空函数用于扩展的一个回调
        onCancelling(cause)
        //回调这个NodeList上的所有监听,所以这类会在这里取消
        notifyHandlers<JobCancellingNode<*>>(list, cause)
        //取消父协程
        cancelParent(cause) 
    }

notifyHandlers<JobCancellingNode<*>>(list, cause)比较简单,这里不在做说明。

 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) }
    }

以上我们知道了父协程取消的时候是通过NodeList取消子协程的.而子协程收到取消后悔递归的取消子协程和自身代码。具体协程的取消细节后续章节在做说明,这里只需要管如何传递取消的。

我们最后看看父协程收到取消异常的之后的处理方法:
cancelParent(cause)

private fun cancelParent(cause: Throwable): Boolean {
        //忽略 这里先当做false,关于范围协程后面有机会再说
        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函数的声明:

public interface ChildHandle : DisposableHandle {
  
    @InternalCoroutinesApi
    public fun childCancelled(cause: Throwable): Boolean
}

我们看下大致的两种实现:

 //JobSupport.kt
 public open fun childCancelled(cause: Throwable): Boolean {
        //(CancellationException异常在手动取消子类的时候抛出)
        //如果子类不是由于意外异常取消的那么不取消父协程,
        if (cause is CancellationException) return true
        //如果子类是由于取消异常之外的情况导致的,比如说子协程除零异常的.那么取消父协程
        return cancelImpl(cause) && handlesException
    }

看下另一种子协程不管如何都不会取消父协程

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

也就是说当我们使用如下代码子协程异常不会取消父协程(父协程依然可以取消子协程).

fun main() {

    //创建一个Job
    val job = GlobalScope.launch {

        launch(SupervisorJob()) {
            val d = 1 / 0
        }

        //为了让子协程完成
        delay(100)

        println("协程完成")
    }

    TimeUnit.SECONDS.sleep(1)
    println("结束")
}

输出:

协程完成
结束

可见子协程异常之后父协程依然在运行.否则就不会出现协程完成这个输出.

我们总结下:

子协程创建时会讲自己放入父协程job链表,所以当父协程取消的时候会回调所有子协程.
子协程取消时候会回调childCancelled函数,父协程根据情况判断是否取消自己

上一篇下一篇

猜你喜欢

热点阅读