一句话结论(记住这个)
在
withThrowingTaskGroup中,只要有一个子任务抛出错误:
1️⃣ 该错误会向外传播
2️⃣ 组内其余所有子任务都会被自动取消
3️⃣ 离开作用域前,Swift 会等待这些任务响应取消并结束
1️⃣ 先看最典型的例子
do {
try await withThrowingTaskGroup(of: Int.self) { group in
group.addTask {
try await work1()
}
group.addTask {
try await work2() // ❌ 这里抛错
}
group.addTask {
try await work3()
}
for try await value in group {
print(value)
}
}
} catch {
print("caught:", error)
}
当 work2() 抛错时,发生的事是:
2️⃣ 运行期到底发生了什么?
✅ 1. 第一个错误被“锁定”
-
第一个抛出的错误:
- 成为 TaskGroup 的失败原因
-
后续错误:
- 会被忽略(但仍会触发取消)
✅ 2. 其他子任务立刻收到取消信号
对其余任务来说:
Task.isCancelled == true
但要注意 ⚠️:
取消是协作式的(cooperative)
-
如果任务:
- 正在
await - 或显式检查
Task.checkCancellation()
- 正在
-
才会尽快退出
✅ 3. group 停止产出新结果
-
for try await value in group:- 会立刻抛出错误
- 不再 yield 剩余结果
✅ 4. 离开作用域前会“收尸”
即使抛错了:
withThrowingTaskGroup { ... }
-
也会等待:
- 所有子任务结束
- 或因取消而终止
👉 不会有“漏网任务”。
3️⃣ 子任务能不能“无视”取消?
理论上可以,但强烈不推荐。
group.addTask {
while true {
// 不 await,不检查取消
}
}
结果:
- TaskGroup 会被迫等它结束
- 你等于“把结构化并发打穿了”
👉 正确做法:
group.addTask {
try Task.checkCancellation()
return try await work()
}
4️⃣ 非 throwing TaskGroup 会怎样?
withTaskGroup(of: Int.self) { group in
group.addTask {
throw MyError() // ❌ 编译错误
}
}
- 不允许抛错
- 必须在子任务内部处理错误
例如:
group.addTask {
do {
return try await work()
} catch {
return -1
}
}
5️⃣ 能不能只取消一部分任务?
不能。
这是 TaskGroup 的设计选择。
TaskGroup 要么整体成功,要么整体失败。
如果你需要更细粒度控制:
- 用多个 TaskGroup
- 或手动管理 Task(不推荐)
6️⃣ 常见误区(非常常见)
❌ 误区 1:已经完成的任务会被“回滚”
不会。
-
已完成任务:
- 结果仍然存在
-
只是你:
- 拿不到它们了
❌ 误区 2:取消 = 立刻终止
不是。
-
Swift 的取消是:
- 标记式
- 协作式
-
没有强杀线程
7️⃣ 官方设计背后的理由
Swift 选择这种行为是为了:
- 避免部分成功 + 部分失败的复杂语义
- 确保资源不会泄漏
- 保证结构化并发的可推理性
你永远知道:
“这个 group 要么给我所有结果,要么抛错。”
8️⃣ 面试级总结(一句话版)
在
withThrowingTaskGroup中,一旦任一子任务抛出异常,其它子任务会被自动取消;错误立即向外传播,TaskGroup 在退出作用域前会等待所有子任务结束或响应取消,从而保证结构化并发的完整性和资源安全。