1️⃣ 核心原则
TaskGroup 中的子任务会自动继承父任务的取消信号,但子任务必须在挂起点(
await或Task.checkCancellation())显式响应取消,才能安全终止并释放资源。
一句话:父任务取消 → 子任务标记为 cancelled → 子任务在挂起点响应 → TaskGroup 安全退出。
2️⃣ 基本示例
try await withThrowingTaskGroup(of: Int.self) { group in
for i in 0..<5 {
group.addTask {
for j in 0..<1000 {
try Task.checkCancellation() // 响应取消
await doWork(i, j)
}
return i
}
}
var results: [Int] = []
for try await value in group {
results.append(value)
}
}
- 父任务或外层 Task 调用
.cancel()→ 所有子任务isCancelled = true - 子任务在
Task.checkCancellation()或await时抛出CancellationError→ 安全退出 - TaskGroup 会等待所有子任务完成或被取消后才返回
3️⃣ 关键注意点
① 必须在挂起点检查取消
-
挂起点包括:
await调用Task.sleepTask.checkCancellation()
-
没有检查的长循环或同步操作不会自动取消,可能阻塞 TaskGroup 退出
group.addTask {
for i in 0..<1_000_000 {
try Task.checkCancellation() // 必须
doHeavyWork(i)
}
}
② 父任务取消 → TaskGroup 自动 propagate
let parentTask = Task {
try await withThrowingTaskGroup(of: Int.self) { group in
group.addTask { await work1() }
group.addTask { await work2() }
return try await group.reduce(0, +)
}
}
parentTask.cancel() // 所有 group 子任务收到取消信号
- 子任务在下一个挂起点响应取消
- 父任务等待所有子任务完成 → TaskGroup 安全退出
- 避免资源泄漏和孤儿任务
③ 使用 withTaskCancellationHandler 做清理(可选)
group.addTask {
try await withTaskCancellationHandler {
print("Clean up resources on cancel")
} operation: {
for i in 0..<1000 {
try Task.checkCancellation()
await doWork(i)
}
}
}
- 保证取消时释放资源
- TaskGroup 退出前会执行清理操作
④ 不要在子任务中做 CPU 密集型同步阻塞
- 同步长计算不会自动响应取消
- 可使用
Task.detached或定期调用Task.checkCancellation()
4️⃣ 总结策略
- 在 TaskGroup 内部添加子任务时,确保子任务在循环或异步操作中定期检查取消
- 使用 await 调用或 Task.checkCancellation() 响应取消
- 父任务取消 → 子任务自动标记 cancelled → TaskGroup 等待所有子任务完成
- 必要时使用 withTaskCancellationHandler 做资源清理
5️⃣ 面试必背总结
在 TaskGroup 中,子任务会继承父任务的取消状态,但必须在挂起点显式检查取消(
await或Task.checkCancellation())。父任务取消会 propagate 到所有子任务,TaskGroup 会等待它们完成或取消,从而保证资源安全和结构化并发正确性。