2-3.【Concurrency】await 调用时,编译器如何处理 suspension point?

1 阅读2分钟

一句话结论

await 是编译器显式标注的 suspension point
编译器在这里把函数拆成前后两个 continuation,
await 之前保存状态,在 await 之后生成一个 resume entry,
运行期通过 swift_task_suspend / resume 把执行权交还给调度器。


1️⃣ 编译器眼中的 await

源码:

let x = await bar()

在 AST / SIL 层面,这一行意味着三件事:

  1. 这里可能中断当前函数
  2. 后续代码不能假设仍在同一线程
  3. 跨越此点的局部变量必须被提升

所以 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)
  • ax 都要 存进 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() 完成:

  1. runtime 找到 continuation
  2. 根据 executor 决定在哪个线程恢复
  3. 调用 resume function
  4. 跳转到对应的 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 继续执行。