2-13.【Concurrency】Task cancellation 与 structured concurrency 有何联系?

2 阅读2分钟

一句话总结

Task cancellation 是结构化并发的天然伴生机制:父任务取消时,其子任务会自动收到取消信号,保证所有任务在作用域结束前要么完成,要么协作退出,从而避免资源泄露和孤儿任务。


1️⃣ 结构化并发与取消的天然联系

结构化并发的原则

  1. 任务树

    • 父任务创建子任务 → 形成父子关系
  2. 生命周期绑定

    • 子任务必须在父任务作用域结束前完成
  3. 错误和取消传播

    • 父任务失败/取消 → 子任务收到通知
  4. 资源安全

    • 子任务不会在父任务结束后“漂浮”

取消是父子传播的机制

  • 父任务调用 cancel()

    • runtime 会遍历父任务的 子任务列表
    • 给每个子任务设置 Task.isCancelled = true
  • 子任务可以在 await 或 checkCancellation() 时响应取消

  • 所以父任务作用域结束 = 子任务完成或被取消


2️⃣ async/await 下的取消行为

let parent = Task {
    async let a = work1()
    async let b = work2()
    
    try await a + b
}

// 取消父任务
parent.cancel()
  • async let 的子任务 a、b 自动收到取消
  • 子任务在 await 或内部检查取消时退出
  • 父任务等待子任务结束 → 作用域安全

TaskGroup 的取消语义

try await withThrowingTaskGroup(of: Int.self) { group in
    for id in ids {
        group.addTask {
            try await work(id)
        }
    }

    for try await value in group { ... } // 自动收集
}
  • 父 Task 被取消或任一子任务 throw:

    • 所有子任务自动被标记为 cancelled
  • 作用域结束前:

    • runtime 等待所有子任务完成或退出
  • 资源不会泄露


3️⃣ Cancellation 的协作式特性

  • 不是强制 kill
  • 子任务必须 显式检查或 await 才能停止
  • Task.checkCancellation() 可以手动触发
  • await 会自动触发 cancellation 检查
group.addTask {
    for i in 0..<1000 {
        try Task.checkCancellation()
        await doWork(i)
    }
}

4️⃣ 父子关系保证取消正确性

  • 父任务 cancel → 子任务收到信号
  • 子任务 exit → 父任务等待(join)
  • 确保没有孤儿任务
  • 保证 Task-local、actor 状态正确释放

5️⃣ 总结关系图

Parent Task
 ├─ Child Task 1  ← cancelleable
 ├─ Child Task 2  ← cancelleable
 └─ Child Task 3  ← cancelleable

Parent.cancel()
→ propagates to all children
→ children check cancellation in await / Task.checkCancellation()
→ children exit
→ parent waits for all children

6️⃣ 面试 / 核心记忆点

  1. Task cancellation 是结构化并发的一部分

    • 父子任务形成树,取消向下传播
  2. 子任务不会逃逸父任务作用域

    • 自动 join → 避免孤儿任务和资源泄露
  3. 协作式取消

    • await/checkCancellation → 响应取消
  4. 与 TaskGroup / async let / Task { } 一致

    • 生命周期 + 错误 + 取消统一管理