一、最标准、推荐的收集方式(for-await)
这是 官方推荐 + 最安全 的方式。
let results: [Int] = await withTaskGroup(of: Int.self) { group in
for id in ids {
group.addTask {
await fetchValue(for: id)
}
}
var collected: [Int] = []
for await value in group {
collected.append(value)
}
return collected
}
关键点
-
group.addTask→ 并发执行 -
for await value in group→ 串行、安全地消费结果 -
返回时:
- 所有子任务已完成
- 不会有悬挂任务
二、结果顺序说明(非常重要 ⚠️)
TaskGroup 不保证返回顺序
addTask 顺序 ≠ 完成顺序 ≠ 收集顺序
for await value in group 的顺序是:
哪个子任务先完成,就先 yield 哪个结果
如果你需要“原始顺序”,看下面第 4 节。
三、收集 + 处理(流式消费)
如果你不只是收集,还要边收边处理:
await withTaskGroup(of: Int.self) { group in
for job in jobs {
group.addTask {
await job.run()
}
}
for await result in group {
updateUI(result) // 顺序执行,安全
}
}
这是 TaskGroup 的黄金用法:
- 子任务并发
- 结果处理串行
- 无共享可变状态
四、保持原始顺序的收集方式
方法 1️⃣:返回 (index, value)
最常见 & 推荐
let results = await withTaskGroup(of: (Int, String).self) { group in
for (index, id) in ids.enumerated() {
group.addTask {
(index, await fetch(id))
}
}
var buffer = Array(repeating: "", count: ids.count)
for await (index, value) in group {
buffer[index] = value
}
return buffer
}
方法 2️⃣:预分配数组 + actor(不推荐)
actor Buffer {
var values: [String]
init(count: Int) {
values = Array(repeating: "", count: count)
}
}
let buffer = Buffer(count: ids.count)
await withTaskGroup(of: Void.self) { group in
for (i, id) in ids.enumerated() {
group.addTask {
let v = await fetch(id)
await buffer.set(i, v)
}
}
}
可行,但比方法 1 复杂,也更慢。
五、Throwing TaskGroup 的收集方式
如果子任务可能抛错:
let results = try await withThrowingTaskGroup(of: Int.self) { group in
for id in ids {
group.addTask {
try await fetchValue(id)
}
}
var collected: [Int] = []
for try await value in group {
collected.append(value)
}
return collected
}
行为说明
-
任一子任务 throw:
- 其余子任务 全部自动取消
-
for try await会立刻抛出错误
六、只等全部完成,不关心结果?
如果你只是想“等它们跑完”:
await withTaskGroup(of: Void.self) { group in
for job in jobs {
group.addTask {
await job.run()
}
}
// 不需要 for await
}
退出作用域 = 所有子任务完成或取消。
七、常见坑(真的很常见)
❌ 1. 在子任务里写共享数组
var results: [Int] = []
withTaskGroup(of: Int.self) { group in
group.addTask {
results.append(await work()) // ❌ 数据竞争
}
}
永远不要这么干。
❌ 2. 在 group 外面等结果
var results: [Int] = []
await withTaskGroup(of: Int.self) { group in
...
}
print(results) // 这里可能是空的
结果应该 在 group 作用域内收敛并返回。
八、速查总结(背这个就够)
在 TaskGroup 中收集子任务返回值的正确方式是:
在withTaskGroup作用域内,用for await串行消费 group 的结果。
TaskGroup 不保证顺序,如需顺序应显式携带 index。
永远不要在子任务中写共享可变状态。