一句话结论
await是编译器显式标注的 suspension point:
编译器在这里把函数拆成前后两个 continuation,
在await之前保存状态,在await之后生成一个 resume entry,
运行期通过swift_task_suspend/resume把执行权交还给调度器。
1️⃣ 编译器眼中的 await
源码:
let x = await bar()
在 AST / SIL 层面,这一行意味着三件事:
- 这里可能中断当前函数
- 后续代码不能假设仍在同一线程
- 跨越此点的局部变量必须被提升
所以 await 是一个 语义边界(semantic boundary) 。
2️⃣ 编译期:suspension point 的 lowering
① 划分 suspension region
编译器会把 async 函数切成多个区段:
[entry]
↓
[suspend #0]
↓
[suspend #1]
↓
[exit]
每一个 await:
- 生成一个 suspend point ID
- 对应一个 resume block
② 提升跨 await 的变量
let a = 1
let x = await bar()
print(a, x)
a、x都要 存进 async context- 不再放在栈上
这一步发生在 SIL 的 AsyncLowering 阶段。
③ 插入状态保存逻辑
在 await 前,编译器生成类似:
context.state = <next_state>
context.resume = <resume_function>
这等价于保存:
- 程序计数器(PC)
- continuation
3️⃣ await 并不是“阻塞等待”
这是理解 suspension point 的关键 ⚠️
await bar() 实际展开为三步:
1. 调用 bar 的 async entry
2. 注册 continuation(当前函数的 resume block)
3. 返回到 runtime(函数挂起)
没有 while-loop、没有 sleep、没有线程等待。
4️⃣ SIL 级别的形态(简化)
假设:
let x = await bar()
use(x)
在 SIL 中,大致会变成:
// 保存恢复点
store state = 1 to context
// 发起异步调用
%cont = function_ref resume_foo
apply bar(%cont)
// 函数在这里“结束”
return
resume_foo:
%x = load context.x
use(%x)
👉 resume block 是一个 正常的 SIL basic block。
5️⃣ 运行期:suspension 是怎么发生的?
当执行到 await:
① 调用 swift_task_suspend
- 把当前 task 标记为 suspended
- 把 continuation 挂到被 await 的任务上
② 当前线程立刻返回调度器
Thread A:
foo()
await bar() ← 线程在这里释放
线程可以立刻执行别的 task。
6️⃣ 恢复(resume)发生了什么?
当 bar() 完成:
- runtime 找到 continuation
- 根据 executor 决定在哪个线程恢复
- 调用 resume function
- 跳转到对应的 resume block
resume foo at state = 1
7️⃣ suspension point 的几个隐藏规则(面试常考)
⚠️ 1. defer 在 suspend 期间不会执行
defer { print("done") }
await foo()
defer在函数 真正返回 时才执行- suspension ≠ return
⚠️ 2. await 是 reentrancy 点
await foo()
state += 1
-
恢复时:
- 全局状态
- actor 状态
- 可能已被其他 task 改变
⚠️ 3. await 是抢占点
- cancellation
- priority
- executor hop
都可能在这里发生
8️⃣ suspension point 与 actor 的关系
@MainActor
func f() async {
await backgroundWork()
updateUI()
}
-
await之后:- runtime 会检查 executor
- 自动 hop 回 MainActor
-
这一步完全是 runtime 决策
9️⃣ 记忆用一句话
await在编译期被 lowering 成一个 suspension point:
保存状态 + 生成 resume block;
在运行期通过 task suspension 把执行权交还给调度器,
恢复时从对应的 resume block 继续执行。