Kotlin 协程原理详解 ------ 以 delay 为例
目录
- 核心结论
- [suspend 函数编译产物:状态机](#suspend 函数编译产物:状态机 "#suspend-%E5%87%BD%E6%95%B0%E7%BC%96%E8%AF%91%E4%BA%A7%E7%89%A9%E7%8A%B6%E6%80%81%E6%9C%BA")
- 挂起全流程
- 恢复全流程
- 关键源码逐层拆解
- 完整时序图
- 一图总结
核心结论
挂起 ≠ 阻塞线程。 挂起本质是:把"回调地址"(Continuation)存起来,然后一路
return,把线程还给线程池。 恢复本质是:定时器到期后,用存好的 Continuation 重新调用状态机,从上次的断点继续。
suspend 函数编译产物:状态机
Kotlin 源码
kotlin
class Test {
fun testDelay() {
GlobalScope.launch {
delay(111) // 挂起点 ①
Log.d(TAG, "testDelay: 1111")
delay(222) // 挂起点 ②
Log.d(TAG, "testDelay: 222")
self() // 挂起点 ③
}
}
suspend fun self() {
delay(333) // 挂起点 ④
Log.d(TAG, "self: 333")
}
}
编译后等价 Java(状态机骨架)
编译器将每个 suspend 调用点编译为一个 label 状态 ,invokeSuspend 通过 switch(label) 实现"从断点继续"。
java
// launch { } 的 lambda 体被编译成这个状态机
Object invokeSuspend(Object $result) {
Object SUSPENDED = IntrinsicsKt.getCOROUTINE_SUSPENDED(); // 哨兵值
switch (label) {
case 0: // 首次执行
throwOnFailure($result);
label = 1; // 保存断点
Object r = delay(111L, this); // 调用 suspend 函数
if (r == SUSPENDED) return SUSPENDED; // ← 挂起点①,直接 return
case 1: // delay(111) 完成后从这里恢复
throwOnFailure($result);
Log.d(TAG, "testDelay: 1111");
label = 2;
r = delay(222L, this);
if (r == SUSPENDED) return SUSPENDED; // ← 挂起点②
case 2: // delay(222) 完成后从这里恢复
throwOnFailure($result);
Log.d(TAG, "testDelay: 222");
label = 3;
r = self(this);
if (r == SUSPENDED) return SUSPENDED; // ← 挂起点③
case 3: // self() 完成后从这里恢复
throwOnFailure($result);
return Unit.INSTANCE; // 协程正常结束
}
}
关键设计:
label在调用suspend函数之前更新,保证挂起后能从正确位置恢复COROUTINE_SUSPENDED是一个全局单例哨兵对象,用于区分"已挂起"和"同步完成"两种情况
挂起全流程
以 delay(111) 为例,从调用到线程释放,共经历 3 层 return。
第一层:delay() 源码
kotlin
// kotlinx/coroutines/Delay.kt
public suspend fun delay(timeMillis: Long) {
if (timeMillis <= 0) return // 不需要等待,直接返回
// suspendCancellableCoroutine 是最底层的挂起原语
// 它会:1. 把当前 Continuation 包装成 CancellableContinuationImpl
// 2. 执行 block(注册定时器)
// 3. 返回 COROUTINE_SUSPENDED
return suspendCancellableCoroutine { cont ->
// 从协程上下文取出 Delay 实现(由 Dispatcher 提供)
// Dispatchers.Default → DefaultDelay(ScheduledThreadPoolExecutor)
// Dispatchers.Main → HandlerContext(Handler.postDelayed)
cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
// ↑ 把 cont(回调地址)交给定时器保管
// 定时器到期后会调用 cont.resumeWith(Unit)
}
// 注意:先注册定时器,再返回 COROUTINE_SUSPENDED
// 顺序不能反!必须先存好回调,再让出线程
}
第二层:invokeSuspend() 接到哨兵值后 return
java
// invokeSuspend 内部,case 0
label = 1;
Object r = DelayKt.delay(111L, this); // delay 返回 COROUTINE_SUSPENDED
if (r == SUSPENDED) return SUSPENDED; // ← invokeSuspend 在这里 return
// 把哨兵值往上传
第三层:BaseContinuationImpl.resumeWith() 接到哨兵值后 return
kotlin
// kotlin/coroutines/jvm/internal/BaseContinuationImpl.kt
// 这是框架的"蹦床驱动器",while 循环驱动状态机
final override fun resumeWith(result: Result<Any?>) {
var current = this
var param = result
while (true) {
val outcome = try {
val outcome = current.invokeSuspend(param) // 调用状态机
if (outcome === COROUTINE_SUSPENDED)
return // ← resumeWith 在这里 return,线程彻底释放
Result.success(outcome)
} catch (e: Throwable) {
Result.failure(e)
}
// 若 invokeSuspend 同步完成(未挂起),继续驱动父 Continuation
val completion = current.completion!!
current = completion as BaseContinuationImpl
param = outcome
}
}
三层 return 示意
kotlin
delay() return COROUTINE_SUSPENDED
│
↓
invokeSuspend() return COROUTINE_SUSPENDED
│
↓
resumeWith() return(退出 while 循环)
│
↓
线程回到线程池,空闲,可执行其他协程
恢复全流程
111ms 后,定时器触发,Continuation 被唤醒,经过以下链路重新执行状态机。
第一步:定时器到期,调用 resumeWith
kotlin
// DefaultDelay 源码(Dispatchers.Default 使用)
override fun scheduleResumeAfterDelay(
timeMillis: Long,
continuation: CancellableContinuation<Unit>
) {
val future = executor.schedule({ // ScheduledThreadPoolExecutor
with(continuation) {
resumeUndispatched(Unit) // 111ms 后这里被执行
}
}, timeMillis, TimeUnit.MILLISECONDS)
// 支持取消:协程取消时同时取消定时器
continuation.invokeOnCancellation {
future.cancel(false)
}
}
第二步:CancellableContinuationImpl 派发到目标线程
kotlin
// CancellableContinuationImpl.resumeWith(简化)
override fun resumeWith(result: Result<Unit>) {
// 检查取消状态
if (cancelled) {
notifyParentOfCancellation()
return
}
// 通过 Dispatcher 把恢复任务投递到协程绑定的线程
// Dispatchers.Default → 扔进线程池队列
// Dispatchers.Main → handler.post(runnable)
dispatcher.dispatch(context, DispatchedContinuation(this, result))
}
第三步:BaseContinuationImpl.resumeWith 再次调用 invokeSuspend
kotlin
// 线程池线程(或主线程)拿到任务后执行
BaseContinuationImpl.resumeWith(Result.success(Unit))
└─ invokeSuspend(Result.success(Unit)) // 再次进入状态机
└─ switch(label = 1) // 从上次断点继续
throwOnFailure($result)
Log.d(TAG, "testDelay: 1111")
label = 2
delay(222, this) ... // 下一个挂起点
关键源码逐层拆解
suspendCancellableCoroutine ------ 挂起原语
kotlin
// kotlin/coroutines/cancellation/CancellableContinuation.kt
public suspend inline fun <T> suspendCancellableCoroutine(
crossinline block: (CancellableContinuation<T>) -> Unit
): T = suspendCoroutineUninterceptedOrReturn { uCont ->
// 1. 用拦截器包装原始 Continuation(使其在正确线程上执行)
val cancellable = CancellableContinuationImpl(uCont.intercepted(), ...)
// 2. 执行 block(注册定时器、注册网络回调等)
block(cancellable)
// 3. 若已经完成(极少数同步场景),直接返回结果
// 否则返回哨兵值,触发挂起
cancellable.getResult() // 通常返回 COROUTINE_SUSPENDED
}
ContinuationImpl ------ 自定义 suspend 函数的 Continuation
每个自定义 suspend fun 都有一个专属的 ContinuationImpl 子类,负责:
- 保存局部变量(相当于栈帧快照)
- 保存
label(断点) - 被唤醒时重入状态机
kotlin
// self() 对应的 Continuation(编译器生成)
private inner class SelfContinuation(completion: Continuation<Unit>)
: ContinuationImpl(completion) {
var result: Any? = null // 上一步的返回值
var label: Int = 0 // 当前断点
var self\$ref: Test? = null // 保存局部变量(L$0)
// 被调度器唤醒时,框架调用此方法
override fun invokeSuspend(result: Any?): Any? {
this.result = result
this.label = this.label or Int.MIN_VALUE // 设最高位,标记"这是恢复调用"
return self(this) // 重入 self() 状态机
}
}
BaseContinuationImpl.resumeWith ------ 蹦床驱动器
kotlin
// 用 while 循环代替递归,避免栈溢出(Trampoline 模式)
final override fun resumeWith(result: Result<Any?>) {
var current = this
var param = result
while (true) {
val outcome = try {
val outcome = current.invokeSuspend(param)
if (outcome === COROUTINE_SUSPENDED) return // 挂起,退出
Result.success(outcome)
} catch (e: Throwable) {
Result.failure(e)
}
// 当前层执行完,向上驱动父 Continuation(调用链)
val completion = current.completion!!
if (completion is BaseContinuationImpl) {
// 父层也是状态机,继续循环(避免递归)
current = completion
param = outcome
} else {
// 到顶了(AbstractCoroutine),通知协程完成
completion.resumeWith(outcome)
return
}
}
}
完整时序图
sequenceDiagram
autonumber
participant U as 调用方线程
participant F as 协程框架
(BaseContinuationImpl) participant T as 定时器线程
(ScheduledExecutor) U->>F: GlobalScope.launch { ... } Note over F: 创建 StandaloneCoroutine
调度到线程池线程 A Note over F: ── 线程池线程 A ── F->>F: resumeWith(Unit) ← 首次启动 F->>F: invokeSuspend(Unit) [label = 0] F->>T: delay(111, this)
scheduleResumeAfterDelay(111ms, cont) Note over T: 保存 cont,注册定时器 T-->>F: return COROUTINE_SUSPENDED F-->>F: return COROUTINE_SUSPENDED Note over F: resumeWith 退出 while 循环
线程 A 释放 ✓ Note over T: ⏱ 111ms 后,定时器到期 T->>F: cont.resumeWith(success(Unit)) Note over F: dispatcher.dispatch(task)
投递到线程池线程 B Note over F: ── 线程池线程 B ── F->>F: invokeSuspend(success) [label = 1] Note over F: throwOnFailure()
Log.d("testDelay: 1111") F->>T: delay(222, this)
scheduleResumeAfterDelay(222ms, cont) T-->>F: return COROUTINE_SUSPENDED Note over F: 线程 B 释放 ✓ Note over T: ⏱ 222ms 后,定时器到期 T->>F: cont.resumeWith(success(Unit)) Note over F: 投递到线程池线程 C Note over F: ── 线程池线程 C ── F->>F: invokeSuspend(success) [label = 2] Note over F: Log.d("testDelay: 222")
进入 self() 状态机 [label = 0] F->>T: delay(333, selfCont)
scheduleResumeAfterDelay(333ms, selfCont) T-->>F: return COROUTINE_SUSPENDED Note over F: 线程 C 释放 ✓ Note over T: ⏱ 333ms 后,定时器到期 T->>F: selfCont.resumeWith(success(Unit)) Note over F: 投递到线程池线程 D Note over F: ── 线程池线程 D ── F->>F: self() 状态机 [label = 1] Note over F: Log.d("self: 333")
return Unit F->>F: while 循环向上驱动
launch body [label = 3]
return Unit F->>U: StandaloneCoroutine.resumeWith(success)
协程生命周期结束 ✓
(BaseContinuationImpl) participant T as 定时器线程
(ScheduledExecutor) U->>F: GlobalScope.launch { ... } Note over F: 创建 StandaloneCoroutine
调度到线程池线程 A Note over F: ── 线程池线程 A ── F->>F: resumeWith(Unit) ← 首次启动 F->>F: invokeSuspend(Unit) [label = 0] F->>T: delay(111, this)
scheduleResumeAfterDelay(111ms, cont) Note over T: 保存 cont,注册定时器 T-->>F: return COROUTINE_SUSPENDED F-->>F: return COROUTINE_SUSPENDED Note over F: resumeWith 退出 while 循环
线程 A 释放 ✓ Note over T: ⏱ 111ms 后,定时器到期 T->>F: cont.resumeWith(success(Unit)) Note over F: dispatcher.dispatch(task)
投递到线程池线程 B Note over F: ── 线程池线程 B ── F->>F: invokeSuspend(success) [label = 1] Note over F: throwOnFailure()
Log.d("testDelay: 1111") F->>T: delay(222, this)
scheduleResumeAfterDelay(222ms, cont) T-->>F: return COROUTINE_SUSPENDED Note over F: 线程 B 释放 ✓ Note over T: ⏱ 222ms 后,定时器到期 T->>F: cont.resumeWith(success(Unit)) Note over F: 投递到线程池线程 C Note over F: ── 线程池线程 C ── F->>F: invokeSuspend(success) [label = 2] Note over F: Log.d("testDelay: 222")
进入 self() 状态机 [label = 0] F->>T: delay(333, selfCont)
scheduleResumeAfterDelay(333ms, selfCont) T-->>F: return COROUTINE_SUSPENDED Note over F: 线程 C 释放 ✓ Note over T: ⏱ 333ms 后,定时器到期 T->>F: selfCont.resumeWith(success(Unit)) Note over F: 投递到线程池线程 D Note over F: ── 线程池线程 D ── F->>F: self() 状态机 [label = 1] Note over F: Log.d("self: 333")
return Unit F->>F: while 循环向上驱动
launch body [label = 3]
return Unit F->>U: StandaloneCoroutine.resumeWith(success)
协程生命周期结束 ✓
一图总结
flowchart TD
A(["`**invokeSuspend** label=0`"])
B["`delay(111, this)`"]
C[/"`scheduleResumeAfterDelay
保存 cont,注册定时器`"/]
D(["`return **COROUTINE_SUSPENDED**`"])
E(["`resumeWith 退出 while
🟢 线程释放`"])
F{{"⏱ 111ms 后"}}
G["`定时器线程
cont.resumeWith(success)`"]
H["`dispatcher.dispatch
投递到目标线程`"]
I(["`**invokeSuspend** label=1
从断点继续执行`"])
A --> B
B --> C
C --> D
D --> E
E -.-> F
F --> G
G --> H
H --> I
I --> |"下一个 suspend 调用"| B
style A fill:#4A90D9,color:#fff,stroke:none
style I fill:#4A90D9,color:#fff,stroke:none
style D fill:#E8A838,color:#fff,stroke:none
style E fill:#27AE60,color:#fff,stroke:none
style F fill:#8E44AD,color:#fff,stroke:none
style C fill:#ECF0F1,stroke:#BDC3C7
style G fill:#ECF0F1,stroke:#BDC3C7
style H fill:#ECF0F1,stroke:#BDC3C7
- 🔵 蓝色:状态机入口(invokeSuspend)
- 🟠 橙色:哨兵值传递(挂起信号)
- 🟢 绿色:线程释放点
- 🟣 紫色:时间流逝 / 定时器到期
三个关键设计原则
| 原则 | 说明 |
|---|---|
| 先存后走 | scheduleResumeAfterDelay 必须在 return COROUTINE_SUSPENDED 之前完成,否则回调地址丢失 |
| 哨兵传递 | COROUTINE_SUSPENDED 是一个对象引用比较(===),逐层 return,不占用任何阻塞资源 |
| 蹦床模式 | resumeWith 用 while 循环代替递归驱动状态机,避免 self() 调 delay() 调下一层时栈溢出 |