一句话概括
在 async/await 中,子任务被设计为父任务作用域的一部分,父任务在离开作用域前会 等待所有子任务完成,从而保证子任务不会在父任务结束后“孤立存在”。
1️⃣ 父任务与子任务的关系
父任务作用域
Task {
Task { await work1() } // 子任务
Task { await work2() } // 子任务
}
- 父任务知道它的子任务是谁
- 运行期维护 Task Tree(父子关系树)
- 父任务在退出作用域前,会 join 所有子任务(等待完成或取消)
TaskGroup / async let 的作用
async let
async let a = fetchA()
async let b = fetchB()
let sum = await a + b
-
async let自动创建 子任务 -
在父任务作用域结束前:
await a + b会 join 这些子任务
-
保证:
- 子任务不会孤立
- 资源安全释放
TaskGroup
await withTaskGroup(of: Int.self) { group in
group.addTask { await work1() }
group.addTask { await work2() }
for await value in group { ... } // 串行消费
} // 作用域结束 = 所有子任务结束
-
group 作用域就是父任务生命周期边界
-
离开作用域前:
- 所有子任务完成
- 或被取消
-
结果收敛在作用域内
2️⃣ 编译器 & runtime 如何保证
① 编译器
-
async let/withTaskGrouplowering 阶段:- 创建 async context / continuation
- 保存局部变量
- 自动 join 子任务
② 运行时
-
Swift runtime 维护 Task Tree
-
父任务退出作用域前:
- 遍历子任务列表
- 等待完成或处理取消
-
异步栈帧恢复时:
- 所有子任务已经被安全清理
-
所以父任务结束 = 子任务完成 + 清理完成
3️⃣ Cancellation & Error Propagation 的配合
- 父任务取消 → 子任务被标记为 canceled
- 子任务抛错 → 父任务捕获,其他子任务自动取消
- TaskGroup / async let 都遵循这一原则
这也是保证子任务安全完成或取消的机制。
4️⃣ 核心记忆点(面试必背)
在 async/await 中,子任务不会在父任务结束后继续运行,因为父任务作用域就是它们的生命周期边界。
编译器 + runtime 自动 join 子任务,并在父任务退出前等待完成或取消,从而保证结构化并发的安全性。