2-12.【Concurrency】在 async/await 中,如何保证子任务在父任务结束前完成?

1 阅读2分钟

一句话概括

在 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 / withTaskGroup lowering 阶段:

    • 创建 async context / continuation
    • 保存局部变量
    • 自动 join 子任务

② 运行时

  • Swift runtime 维护 Task Tree

  • 父任务退出作用域前:

    • 遍历子任务列表
    • 等待完成或处理取消
  • 异步栈帧恢复时:

    • 所有子任务已经被安全清理
  • 所以父任务结束 = 子任务完成 + 清理完成


3️⃣ Cancellation & Error Propagation 的配合

  • 父任务取消 → 子任务被标记为 canceled
  • 子任务抛错 → 父任务捕获,其他子任务自动取消
  • TaskGroup / async let 都遵循这一原则

这也是保证子任务安全完成或取消的机制。


4️⃣ 核心记忆点(面试必背)

在 async/await 中,子任务不会在父任务结束后继续运行,因为父任务作用域就是它们的生命周期边界。
编译器 + runtime 自动 join 子任务,并在父任务退出前等待完成或取消,从而保证结构化并发的安全性。