Kotlin協程-協程的內部概念Continuation

一個協程的生命週期是這樣的,

±---------+        ±---------------------+
| START |----------------------->| SUSPENDED |
±---------+        ±---------------------+
               |  ^
               V  |
              ±-----------+ completion invoked ±----------------+
              |RUNNING|------------------------->| COMPLETED |
              ±-----------+         ±--------------------+

協程的重點是可以在掛起和運行兩個狀態中切換。實現這個能力的關鍵在於協程實現了continuation接口。

可重入性

之前的分析裏說過continuation接口,這篇着重分析它的設計邏輯,

public interface Continuation<in T> {
    public val context: CoroutineContext
    public fun resumeWith(result: Result<T>)
}

resumeWith是協程的重點,每次切出去到suspend狀態,再進入running狀態都是通過resumeWith接口。我們所寫的每一個coroutine,都會continuation接口。

有意思的是,它是什麼時候實現的,怎麼實現的?

在launch{}的源碼裏可以看到有個block參數,這個block就是我們所寫的協程代碼,

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
}

block具體是個什麼東西,得看編譯後的字節碼。從協程源碼裏猜測,它肯定是一個實現了continuation的類,因此它纔能有可重入性。

把編譯後的字節碼用jd-gui打開可以看到,我們所寫的協程會給編譯器插入代碼,實現SuspendLambda接類,
suspend lambda

在編譯後的字節碼裏見不到resumeWith()函數,說明這個函數必定是在SuspendLambda中有實現。

而SuspendLabmda是在標準庫裏的,ContinuationImpl.kt文件下,

internal abstract class SuspendLambda(
    public override val arity: Int,
    completion: Continuation<Any?>?
) : ContinuationImpl(completion), FunctionBase<Any?>, SuspendFunction {
    constructor(arity: Int) : this(arity, null)

    public override fun toString(): String =
        if (completion == null)
            Reflection.renderLambdaToString(this) // this is lambda
        else
            super.toString() // this is continuation
}

如果從這裏往上追進去,會在 BaseContinuationImpl 類下面發現 resumeWith()接口。

internal abstract class BaseContinuationImpl(
    // This is `public val` so that it is private on JVM and cannot be modified by untrusted code, yet
    // it has a public getter (since even untrusted code is allowed to inspect its call stack).
    public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {
    // This implementation is final. This fact is used to unroll resumeWith recursion.
    public final override fun resumeWith(result: Result<Any?>) {
        // This loop unrolls recursion in current.resumeWith(param) to make saner and shorter stack traces on resume
        var current = this
        var param = result
        while (true) {
            // Invoke "resume" debug probe on every resumed continuation, so that a debugging library infrastructure
            // can precisely track what part of suspended callstack was already resumed
            probeCoroutineResumed(current)
            with(current) {
                val completion = completion!! // fail fast when trying to resume continuation without completion
                val outcome: Result<Any?> =
                    try {
                        val outcome = invokeSuspend(param) //調用class裏的invokeSuspend

最後一行會調用 invokeSuspend。這個調用鏈可以看出我們所寫的協程的可重入性是怎麼實現的了。

從block到coroutine

上面的分析,只展現了一個block目前所具有的特點。雖然它具有了可重入性,但它還沒有可被攔截的能力,也就是Intercept。什麼時候變成可以攔截呢。在Cancellable.kt裏,可以看到,

internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(receiver: R, completion: Continuation<T>) =
    runSafely(completion) {
        createCoroutineUnintercepted(receiver, completion).intercepted().resumeCancellableWith(Result.success(Unit))
    }

createCoroutineUnintercepted創建了一個coroutine。顧名思義,這時候是一個未攔截的coroutine。這個函數又再次是 kotlin 標準庫裏的。它在 IntrinsicsNative.kt 中,

@SinceKotlin("1.3")
@Suppress("UNCHECKED_CAST")
public actual fun <R, T> (suspend R.() -> T).createCoroutineUnintercepted(
        receiver: R,
        completion: Continuation<T>
): Continuation<Unit> {
    val probeCompletion = probeCoroutineCreated(completion)
    return if (this is BaseContinuationImpl)
        create(receiver, probeCompletion) //調用create函數
    else {
        createCoroutineFromSuspendFunction(probeCompletion) {
            (this as Function2<R, Continuation<T>, Any?>).invoke(receiver, it)
        }
    }
}

現在它會去調用create函數。而create函數又在哪呢?在編譯後的字節碼裏。
create

再一次,kotlin編譯器在編譯過程幫我們插入了這段代碼。從設計上來看,create其實是做了一次封裝,把需要的對象通過參數傳進去。

現在的block距離真正意義上的coroutine,還差一個可派發性。雖然它已經具有了可重入,可攔截,還差一點。

攔截器-Interceptor

block的可派發性是在 Cancellable.kt 的 intercept() 函數賦予的。上面創建完的block,是一個繼承了SuspendLambda的block,而繼承樹上和intercept()有關的是ContinuationImpl,

internal abstract class ContinuationImpl(
    completion: Continuation<Any?>?,
    private val _context: CoroutineContext?
) : BaseContinuationImpl(completion) {
    constructor(completion: Continuation<Any?>?) : this(completion, completion?.context)

    public override val context: CoroutineContext
        get() = _context!!

    @Transient
    private var intercepted: Continuation<Any?>? = null

    public fun intercepted(): Continuation<Any?> =
        intercepted
            ?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
                .also { intercepted = it }

最後這裏,是真正給block賦予可派發性的地方。context[ContinuationInterceptor]獲取的是最開始給上下文設定的Dispatcher,不管是 DefaultScheduler,還是EventLoop,他們都有個公有的父類CoroutineDispatcher。而interceptContinuation的唯一實現就在這個類裏。

public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
    DispatchedContinuation(this, continuation)

這裏可以看到它做了一次代理模式,把Dispatcher封裝了進去,作爲 DispatchedContinuation的一個成員。

至此一個block就完成了它的整個創建過程。從一個block,到支持可重入,到支持可攔截,最後支持可派發。

所以會看到雖然協程的外部概念很清晰,只是一個 coroutine,但在協程內部,實際上支撐它的還有Continuation,Dispatch,Intercept。還是挺複雜的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章