Kotlin 协程源码解析:DispatchedContinuation 里的 Dispatcher 从哪里来?
之前研究协程恢复的时候,我们已经知道了一个很重要的对象:DispatchedContinuation。
当协程需要切换线程恢复时,它会参与后面的调度流程,最终进入 CoroutineScheduler,再由线程执行 DispatchedContinuation.run()。
但当时还留下了一个问题:
DispatchedContinuation 是什么时候创建出来的?
尤其是它里面的 dispatcher:
scss
DispatchedContinuation(
dispatcher,
continuation
)
这个 dispatcher 到底是从哪里来的?
这次我们就从 launch(Dispatchers.IO) 开始,顺着源码往回追。
从 launch 开始
假设我们有这样一段代码:
scss
launch(Dispatchers.IO) {
println("hello")
}
launch 最开始会创建一个 StandaloneCoroutine:
ini
val coroutine = StandaloneCoroutine(
newContext,
active = true
)
这里的 newContext 就包含了我们传入的 Dispatchers.IO。
随后:
lua
coroutine.start(start, coroutine, block)
默认情况下,CoroutineStart 是 DEFAULT,最终会进入:
scss
block.startCoroutineCancellable(receiver, completion)
继续往下:
scss
createCoroutineUnintercepted(receiver, completion)
.intercepted()
.resumeCancellableWithInternal(Result.success(Unit))
真正有意思的地方出现了:
scss
.intercepted()
所以接下来我们只追这个 intercepted()。
先创建一个 Continuation
createCoroutineUnintercepted() 最终会走到 JVM 的实现。
对于普通的 suspend lambda,它会调用:
javascript
createCoroutineFromSuspendFunction(
completion
) {
...
}
这里有一段非常关键的代码:
kotlin
val context = completion.context
return object : ContinuationImpl(
completion as Continuation<Any?>,
context
) {
...
}
也就是说,协程真正开始执行之前,首先创建了一个 ContinuationImpl。
而这个 ContinuationImpl 的 context:
ContinuationImpl.context
来自:
completion.context
那么这个 completion 是谁?
回到前面:
ini
StandaloneCoroutine(newContext, active = true)
也就是说,completion 本身就是我们刚刚创建的协程对象,它持有的 CoroutineContext 中已经包含了:
Dispatchers.IO
因此,到这里可以画出一条很重要的关系:
scss
launch(Dispatchers.IO)
↓
StandaloneCoroutine
↓
completion.context
↓
ContinuationImpl.context
Dispatcher 并不是在 intercepted() 里面才被放进去的。
它在更早的时候,就已经存在于这个 Continuation 的 CoroutineContext 中了。
intercepted() 做了什么?
接下来就是我们真正要找的代码:
kotlin
public fun intercepted(): Continuation<Any?> =
_intercepted
?: (context[ContinuationInterceptor]
?.interceptContinuation(this) ?: this)
.also { _intercepted = it }
这里其实做了两件事。
第一件:
css
context[ContinuationInterceptor]
从当前 Continuation 的 CoroutineContext 中找到 ContinuationInterceptor。
我们不需要在这里深入 CombinedContext 的链表结构,也不需要研究 Key 的具体实现,只需要知道:
vbnet
ContinuationInterceptor.Key
可以从 Context 中找到对应的 Element。
而 CoroutineDispatcher 本身就是 ContinuationInterceptor。
因此,对于:
scss
launch(Dispatchers.IO)
这里最终拿到的就是对应的 Dispatchers.IO。
于是代码实际上可以近似理解成:
kotlin
Dispatchers.IO.interceptContinuation(this)
真正的关键就在这里。
Dispatcher 创建了 DispatchedContinuation
CoroutineDispatcher 对 ContinuationInterceptor 的实现是:
kotlin
public final override fun <T> interceptContinuation(
continuation: Continuation<T>
): Continuation<T> =
DispatchedContinuation(this, continuation)
终于找到答案了。
这里的:
kotlin
this
就是当前的 CoroutineDispatcher。
而第二个参数:
continuation
就是刚才创建出来的 ContinuationImpl。
所以:
kotlin
DispatchedContinuation(this, continuation)
实际上就是:
ini
DispatchedContinuation(
dispatcher = Dispatchers.IO,
continuation = ContinuationImpl
)
这也解释了一个之前容易产生误解的地方:
DispatchedContinuation 并不是自己去寻找 Dispatcher。
真正的过程是:
scss
ContinuationImpl
│
│ intercepted()
↓
CoroutineContext
│
│ [ContinuationInterceptor]
↓
Dispatchers.IO
│
│ interceptContinuation(this)
↓
DispatchedContinuation
Dispatcher 作为 ContinuationInterceptor 存在于 CoroutineContext 中。
当 ContinuationImpl 被 intercepted() 时,它从 Context 中找到这个 Dispatcher,然后把自己交给 Dispatcher:
kotlin
interceptContinuation(this)
最后由 Dispatcher 创建出:
DispatchedContinuation
这和我们之前研究的恢复流程接起来了
到这里,我们其实把之前研究过的两段源码接起来了。
前面我们研究过:
markdown
协程恢复
↓
DispatchedContinuation
↓
dispatch
↓
CoroutineScheduler
↓
TaskImpl
↓
DispatchedContinuation.run()
这次我们补上了 DispatchedContinuation 的出生过程:
scss
launch(Dispatchers.IO)
↓
CoroutineContext
↓
ContinuationImpl
↓
intercepted()
↓
找到 Dispatchers.IO
↓
interceptContinuation()
↓
DispatchedContinuation
↓
...
↓
CoroutineScheduler
↓
DispatchedContinuation.run()
这样一来,之前看起来有些零散的源码终于连成了一条线。
我们之前知道的是:
协程恢复以后,Dispatcher 最终会把任务交给线程调度器。
现在又知道了另一半:
这个 Dispatcher 其实早就存在于 CoroutineContext 中,在协程启动时通过
intercepted()被取出来,并参与创建DispatchedContinuation。
所以 DispatchedContinuation 可以看成是连接两边的一个关键对象:
markdown
CoroutineContext
│
│ 提供 Dispatcher
↓
DispatchedContinuation
│
│ 负责后续恢复/调度
↓
CoroutineScheduler
到这里,我们关于"Dispatcher 是怎么进入 DispatchedContinuation 的"这个问题,也就算真正闭环了。