Kotlin 协程源码解析:DispatchedContinuation 里的 Dispatcher 从哪里来?

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 的"这个问题,也就算真正闭环了。

相关推荐
打工仔折腾 AI1 小时前
从BPE到SentencePiece:Transformer分词原理与Python实战对比
android·人工智能·python·深度学习·langchain·transformer·ai agent 实战
Dovis(誓平步青云)2 小时前
几个方案来回选不定?做一个随时切换的候选推荐页
android·java·服务器·开发语言·javascript·数据库·智能化
恋猫de小郭6 小时前
KMP 又改了编译流程, Separate Compilation 禁止了 `commonMain` 的依赖穿透
android·前端·flutter
执明wa6 小时前
Android RecyclerView 实战:点击频道栏切换对应内容
android·开发语言·windows·microsoft·android studio
新鲜势力呀6 小时前
PHP 定时数据同步实战:从第三方接口超时到异步同步 + 增量更新架构优化全过程
android
星间都市山脉8 小时前
RecoveryUI 圆角屏安全边距配置及代码调用链
android·java·linux·windows·ubuntu
打工仔折腾 AI8 小时前
DiPlay 实测:iPhone 绕过硬件盒子直连 BYD 车机的思路拆解
android·人工智能·后端·python·gradle·iphone·ai agent 实战
hengdonghui9 小时前
Writeup 4 NUAA 2017 robots
android·ctf·re