Kotlin 协程源代码泛读:Continuation

要想深入理解 Kotlin 协程,Continuation 是个绕不开的概念

这篇文章中我尝试通过示例结合源码解读一下 Continuation

首先浏览一下 Continuation 的定义,以及框架提供的一些扩展方法

kotlin 复制代码
// Continuation.kt
/**
 * Interface representing a continuation after a suspension point that returns a value of type `T`.
 */

@SinceKotlin("1.3")
public interface Continuation<in T> {
    /**
     * The context of the coroutine that corresponds to this continuation.
     */
    public val context: CoroutineContext

    /**
     * Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the
     * return value of the last suspension point.
     */
    public fun resumeWith(result: Result<T>)
}

我猜官方的注释,对于初见协程的「小白」应该是没啥用...

css 复制代码
Interface representing a continuation after a suspension point that returns a value of type T

先把两个名词记在心里: continuation & suspension point

回到正题,既然 Continuation 是个 interface,就一定有它的实现类,通过 IDE 可以快捷查看到它有一堆的 implemention,其中 90% 都是 XXXCoroutine(协程),之前的系列文章也反复提到过

python 复制代码
Coroutine is a Continuation

接着往下看一些 suspend fun 的扩展方法,主要是一些工具方法,可以直接调用,它们将 suspend fun 转换成 Continuation(协程),隐藏了 Continuation/Coroutine 的实现细节

kotlin 复制代码
public fun <T> (suspend () -> T).createCoroutine(
    completion: Continuation<T>
): Continuation<Unit> =
    SafeContinuation(
        createCoroutineUnintercepted(completion)
            .intercepted(), COROUTINE_SUSPENDED
    )

这个 createCoroutine 扩展方法根据 suspend fun 创建一个协程(字面上),但是它明明返回的是 Continuation...,emm Coroutine is a Continuation SafeContinuation 这种冠以Safe前缀的类名,可以猜出它是一个 Wrapper,用于「安全」的「执行」Continuation,create & intercepted 在之前「引子」那篇文章里提过,属于创建协程的一键三连(create & intercepted & resume),只是这里把 resume 留给使用者,

下面是一段测试代码

kotlin 复制代码
fun testCreateCoroutine() {
    suspend {
        log("suspend, createCoroutine")
    }.createCoroutine(
        Continuation(
            context = Dispatchers.IO,
            resumeWith = {
                log("complete")
            }
        )
    ).resume(Unit)
}

再看另一个扩展方法

kotlin 复制代码
public fun <R, T> (suspend R.() -> T).startCoroutine(
    receiver: R,
    completion: Continuation<T>
) {
    createCoroutineUnintercepted(receiver, completion).intercepted().resume(Unit)
}

startCoroutine 通过suspend fun 启动一个协程,suspend fun 可以有一个接收者 R,并通过 completion 接收协程的执行结果

kotlin 复制代码
fun testStartCoroutineWithReceiver() {
    class Receiver {
        suspend fun doSuspend() {
            log("Receiver.doSuspend")
        }
    }

    Receiver::doSuspend.startCoroutine(
        receiver = Receiver(),
        Continuation(
            context = Dispatchers.IO,
            resumeWith = {
                log("complete")
            }
        )
    )
}

Continuation.kt 先讲到这;我们顺着类继承体系,顺藤摸瓜,先看看它的一个直接实现 BaseContinuationImpl

kotlin 复制代码
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?>) {
        ...
    }

    protected abstract fun invokeSuspend(result: Result<Any?>): Any?
    protected open fun releaseIntercepted() {
        // does nothing here, overridden in ContinuationImpl
    }

    // 这两个方法,子类必须实现
    public open fun create(completion: Continuation<*>): Continuation<Unit> {
        throw UnsupportedOperationException("create(Continuation) has not been overridden")
    }

    public open fun create(value: Any?, completion: Continuation<*>): Continuation<Unit> {
        throw UnsupportedOperationException("create(Any?;Continuation) has not been overridden")
    }
}

我们看一个类的源代码,可以通过它的构造函数看它需要啥,通过内部字段看它都有哪些「状态」,然后通过方法观其行为

BaseContinuationImpl 需要一个 completeion 的 Continuation 构造参数,框架这只是说明为什么它是 val 的,怕恶意代码通过奇技淫巧修改它实现一些酷炫的效果! 这个参数还有另一层意义,它使得 Continuation 可以组成一个链式(link)结构,就像函数调用一样(caller stack)

BaseContinuationImpl 的核心职责是实现了 resumeWith 方法的架子,它初见依然容易被劝退,抓住主线:调用invokeSuspend 方法,这个命名你能想到啥?对,就是调用 suspend fun

kotlin 复制代码
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)
                        if (outcome === COROUTINE_SUSPENDED) return
                        Result.success(outcome)
                    } catch (exception: Throwable) {
                        Result.failure(exception)
                    }
                releaseIntercepted() // this state machine instance is terminating
                if (completion is BaseContinuationImpl) {
                    // unrolling recursion via loop
                    current = completion
                    param = outcome
                } else {
                    // top-level completion reached -- invoke and return
                    completion.resumeWith(outcome)
                    return
                }
            }
        }
}

invokeSuspend 如何调用 suspend fun 呢?之前系列文章中反编译过的一段 suspend 代码

kotlin 复制代码
suspend fun doSuspend() {
    delay(1000L)
    println("doSuspend")
}

反编译后:

less 复制代码
   @Nullable
   public static final Object doSuspend(@NotNull Continuation $completion) {
      Continuation $continuation;
      label20: {
         ...
         // ContinuationImpl 继承自 BaseContinuationImpl
         $continuation = new ContinuationImpl($completion) {
            // $FF: synthetic field
            Object result;
            int label;
            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               this.result = $result;
               this.label |= Integer.MIN_VALUE;
               // 调用 suspend fun
               return SuspendKt.doSuspend((Continuation)this);
            }
         };
      }
}

每个 suspend 函数内部都有一个 Continuation 对象,它就像令牌一样,驱动 suspend 函数(状态机)执行 总结一下 BaseContinuationImpl 实现了 resumeWith 代码框架

注意到suspend 函数没有直接创建 BaseContinuationImpl,而是创建了 ContinuationImpl 类对象,它是 BaseContinuationImpl 的直接子类,实现了我们之前提过的创建协程一键三连 create/intercepted/resume 中的 intercepted

kotlin 复制代码
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
 
    // 还记得这个 intercepted 方法调用地方吗?
    // intercepted 方法查询协程上下文中的 ContinuationInterceptor
    // 将自己包装成「可调度」的协程
    public fun intercepted(): Continuation<Any?> =
        intercepted
            ?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
                .also { intercepted = it }

    protected override fun releaseIntercepted() {
        val intercepted = intercepted
        if (intercepted != null && intercepted !== this) {
            context[ContinuationInterceptor]!!.releaseInterceptedContinuation(intercepted)
        }
        this.intercepted = CompletedContinuation // just in case
    }
}

CoroutineInterceptor 拦截器使用了拦截器模式,使得我们可以拦截(监听)Continuation 的 resumeWith(所谓的恢复执行),在使用协程的过程中接触过 Dispatcher(.IO, .Main),它就是一种 CoroutineInterceptor

kotlin 复制代码
/**  
 * Returns a continuation that wraps the provided [continuation], thus intercepting all resumptions.  
 *  
 * This method should generally be exception-safe. An exception thrown from this method  
 * may leave the coroutines that use this dispatcher in the inconsistent and hard to debug state.  
 */  
public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =  
    DispatchedContinuation(this, continuation)

总结一下目前我们认识的 Continuation(类继承体系)

复制代码
Continuation
  BaseContinuationImpl
     ContinuationImpl
  DispatchedContinuation

DispatchedContinuation 没有继承自 ContinuationImpl 而是使用了组合(代理)

kotlin 复制代码
internal class DispatchedContinuation<in T>(
    @JvmField internal val dispatcher: CoroutineDispatcher,
    // Used by the IDEA debugger via reflection and must be kept binary-compatible, see KTIJ-24102
    @JvmField val continuation: Continuation<T>

) : DispatchedTask<T>(MODE_UNINITIALIZED), CoroutineStackFrame, Continuation<T> by continuation {
    // 使用 dispatcher(调度器)
    // 将协程调度到线程池中的 Worker 线程
    override fun resumeWith(result: Result<T>) {
        val context = continuation.context
        val state = result.toState()
        if (dispatcher.isDispatchNeeded(context)) {
            _state = state
            resumeMode = MODE_ATOMIC
            // 调度!
            dispatcher.dispatch(context, this)
        } else {
            executeUnconfined(state, MODE_ATOMIC) {
                withCoroutineContext(this.context, countOrElement) {
                    continuation.resumeWith(result)
                }
            }
        }
    }
}

DispatchedContinuation 还实现了 DispatchedTask 类,命名很规范,一个可调度的任务,关于调度器我们先挂起不展开...

记得我们之前提过 协程(Coroutine) is-a Continuation! 综上 Continuation 类集成体系:

复制代码
Continuation
  BaseContinuationImpl
     ContinuationImpl
  DispatchedContinuation
  AbstractCoroutine
     XXXCoroutine

跑个题,为什么协程要是一个 Continuation?

我们看看 AbstractCotourtine 的 resumeWith 方法

kotlin 复制代码
public final override fun resumeWith(result: Result<T>) {
    val state = makeCompletingOnce(result.toState())
    if (state === COMPLETING_WAITING_CHILDREN) return
    afterResume(state)
}

AbstractCoroutine 通过实现 Continuation,相当于起到一个调用链表头的角色!当所有的协程/Continuation 执行后它会得到通知,这也就是文档里说的「结构化」

做了一堆理论上的分析,我们再来写一些搞怪的测试代码,当一回「孔乙己」

实际开发并不会这么写,除非你要写一些框架层面的代码,或者是一些 hack 场景

直接执行suspend 函数

理解了 suspend 函数和 Continuation 之后,会明白它不一定需要在 Coroutine(scope)中执行,直接转换成带 Continuation 参数的 Function,代价是必须通过 complete Continuation 获取执行结果

kotlin 复制代码
fun testInvokeSuspendDirect() {
    (::doSuspend as Function1<Continuation<Unit>, Unit>).invoke(
        Continuation(
            context = EmptyCoroutineContext,
            resumeWith = {
                println("complete")
            }
        )
    )
    Thread.sleep(1000L)
}

suspend fun doSuspend() {
    delay(1000L)
    println("doSuspend")
}
通过 suspendCorouitne 方法获取当前 suspend 函数的 Continuation
kotlin 复制代码
fun testSuspendCoroutine() {
    runBlocking {
        suspendCoroutine<Unit> { continuation ->
            log("continuation ${continuation::class.java.simpleName}")
            log("do something")
            continuation.resume(Unit)
        }
        log("resumed")
        // do something
        log("complete")
    }
}
将普通函数伪装成 suspend fun
kotlin 复制代码
fun testMockSuspend() {
    runBlocking {
        (::doMockSuspend as suspend () -> Unit)()
        println("done")
    }
}

fun doMockSuspend(continuation: Continuation<Int>) {
    println("doSomething")
    Thread.sleep(1000L)
    println("doMockSuspend")
}

如果你的想象力足够丰富,还可以写出其它不符合直觉的代码,哈哈

待续~

相关推荐
婵鸣空啼4 小时前
GD图像处理与SESSiON
android
sunly_4 小时前
Flutter:导航固定背景图,滚动时导航颜色渐变
android·javascript·flutter
用户2018792831675 小时前
简单了解android.permission.MEDIA_CONTENT_CONTROL权限
android
_一条咸鱼_5 小时前
Android Runtime类卸载条件与资源回收策略(29)
android·面试·android jetpack
顾林海5 小时前
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
android·面试·性能优化
砖厂小工5 小时前
Now In Android 精讲 8 - Gradle build-logic 现代构建逻辑组织方式
android
玲小珑5 小时前
Auto.js 入门指南(八)高级控件与 UI 自动化
android·前端
harry235day6 小时前
Compose 带动画的待办清单列表页
android·android jetpack
vocal6 小时前
我的安卓第一课:四大组件之一Activity及其组件RecyclerView
android
咕噜企业签名分发-淼淼6 小时前
如何实现安卓端与苹果端互通的多种方案
android