要想深入理解 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")
}
如果你的想象力足够丰富,还可以写出其它不符合直觉的代码,哈哈
待续~