1️⃣ 核心结论
在 Swift Concurrency 中,任务取消是协作式的,并通过父子任务树向下传播:父任务取消 → 所有直接和间接子任务收到取消信号 → 子任务在挂起点检查取消状态并自行终止。
一句话:取消是“向下传递的协作信号”,不是强制杀死线程。
2️⃣ 父子任务关系与结构化并发
-
Swift 使用 结构化并发(Structured Concurrency)管理任务生命周期
-
父任务负责创建子任务:
async let子任务TaskGroup内的任务
-
父任务取消 → 子任务自动收到信号
Task {
async let a = workA()
async let b = workB()
try await a + b
}.cancel() // 父任务取消
a、b都会被标记为取消- 在
await或Task.checkCancellation()时响应
3️⃣ 协作式取消机制
-
Swift 取消是 协作式,不是强制杀死线程
-
子任务必须显式检查:
await时自动检查Task.checkCancellation()手动检查
Task {
for i in 0..<1000 {
try Task.checkCancellation()
await doWork(i)
}
}
- 如果父任务取消 →
Task.checkCancellation()会抛CancellationError - Task 可以安全终止,不会留下孤儿任务
4️⃣ TaskGroup 中的取消传播
await withThrowingTaskGroup(of: Int.self) { group in
for i in 0..<5 {
group.addTask { await work(i) }
}
// 取消父任务
}
- 父任务取消 → 所有 group 内子任务标记取消
- runtime 会将取消信号传递到每个子任务
- 子任务在挂起点响应 → 终止或抛异常
- group 在退出前会等待所有子任务完成或取消
5️⃣ DetachedTask 的区别
Task.detached不属于父任务树- 不自动继承父任务取消
- 取消必须手动控制:
let detached = Task.detached {
for i in 0..<1000 {
try Task.checkCancellation()
await doWork(i)
}
}
detached.cancel() // 需要显式取消
6️⃣ Runtime 的底层原理
- 父任务取消 → runtime 标记父任务状态为 canceled
- 遍历父任务的 子任务列表,逐一标记
isCancelled = true - 子任务在下一个挂起点(
await或Task.checkCancellation())检查状态 - 子任务响应取消 → 抛出
CancellationError或安全退出 - 父任务等待所有子任务完成或取消(join),保证结构化并发安全
7️⃣ 类比理解
- 父任务 = “主管”
- 子任务 = “员工”
- 父任务取消 = “主管下达撤退信号”
- 子任务挂起点 = “员工收到信号,可以安全停止工作”
8️⃣ 面试必背总结
- Swift 任务取消是 协作式,不是强制杀死线程
- 父子任务关系:父任务取消 → 子任务标记取消
- 挂起点检查:await 或 Task.checkCancellation() 响应
- TaskGroup / async let 都遵循取消传播
- DetachedTask 不继承父任务取消,需要手动控制