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

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

待续~

相关推荐
JhonKI2 小时前
【MySQL】存储引擎 - CSV详解
android·数据库·mysql
开开心心_Every2 小时前
手机隐私数据彻底删除工具:回收或弃用手机前防数据恢复
android·windows·python·搜索引擎·智能手机·pdf·音视频
大G哥3 小时前
Kotlin Lambda语法错误修复
android·java·开发语言·kotlin
鸿蒙布道师6 小时前
鸿蒙NEXT开发动画案例2
android·ios·华为·harmonyos·鸿蒙系统·arkui·huawei
androidwork6 小时前
Kotlin Android工程Mock数据方法总结
android·开发语言·kotlin
xiangxiongfly9158 小时前
Android setContentView()源码分析
android·setcontentview
悠哉清闲9 小时前
kotlin一个函数返回多个值
kotlin
人间有清欢10 小时前
Android开发补充内容
android·okhttp·rxjava·retrofit·hilt·jetpack compose
人间有清欢10 小时前
Android开发报错解决
android
每次的天空12 小时前
Android学习总结之kotlin协程面试篇
android·学习·kotlin