TaskGroup 和 async let 都是用于并发并行执行多个异步任务的工具,但它们各自适用于不同的场景。
async let:并发启动多个已知数量的异步任务,适合数量固定、结构清晰的并发。
TaskGroup:动态创建多个异步任务并等待全部完成,适合数量不定、动态生成的任务。
async let
并发执行多个已知任务。
async let image1 = loadImage(from: "url1")
async let image2 = loadImage(from: "url2")
let result = try await [image1, image2]
另一种写法:
async let image1 = loadImage(from: "url1")
async let image2 = loadImage(from: "url2")
let result1 = try await image1
let result2 = try await image2
启动三个异步任务并发执行,而不是顺序。
最终使用 await 等待所有任务结果。
适合任务数量确定且类型一致(如并发加载多个图片)。
注意:不能用for循环(数量必须提前写死。)
TaskGroup
TaskGroup 是 Swift 结构化并发模型中的一种并发原语,用于管理“一组可以并发执行的异步子任务”。
func loadNumbers() async -> [Int] {
await withTaskGroup(of: Int.self) { group in
// 提交 2 个并发任务
group.addTask { 1 }
group.addTask { 2 }
var results: [Int] = []
// 逐个“按完成顺序”接收结果
for await value in group {
results.append(value)
}
return results
}
}
// 调用
Task {
let values = await loadNumbers()
print(values)
}
1、withThrowingTaskGroup(for: Int.self):创建一个任务组(所有任务一起并发执行,支持抛错),指定每个子任务返回Int。
因为withThrowingTaskGroup是一个泛型函数,TaskGroup必须知道“每个子任务返回的类型”,因此需要指定类型,否则编译器会因为无法推断类型而报错。
2、group.addTask { … }:向当前任务组提交一个新的并发子任务。
当调用addTask时:
group.addTask { 1 }
立即创建一个并发子任务,子任务开始执行,当前协程不会等待任务完成,子任务的返回值通过 for await 或 next() 取出。
因此,当调用 addTask 时,提交任务并立即启动,不阻塞当前代码。
3、for await value in group:等待并处理每个子任务返回的值,当某个子任务完成,就会产生一个值并进入迭代,当全部子任务完成后,循环结束。
for-await循环会等待全部子任务完成后,再调用return返回。
伪代码逻辑:
进入 TaskGroup 组
提交并发子任务 1(子任务开始执行)
提交并发子任务 2(子任务开始执行)
循环:
等待并处理子任务
添加返回值
循环结束:所有子任务完成
最后,返回数组
1、Task Group的next()方法:
next() 方法用来“手动拉取下一个已完成子任务的结果”。
调用 next() 会挂起当前任务,直到某个子任务“先完成”为止。
当子任务完成后,返回子任务的结果。当组内所有任务都完成时,返回 nil。
while let value = await group.next() {
results.append(value)
}
等价于:
for await value in group {
results.append(value)
}
可以把for-in循环理解为语法糖,本质等价于持续调用 await group.next(),直到返回 nil。
2、失败场景:
如果允许失败的TaskGroup(返回Optional),则可以使用:
await withTaskGroup(of: String?.self) { group in
for i in 1...3 {
group.addTask {
await loadMaybe(id: i)
}
}
}
func loadMaybe(id: Int) async -> String? {
if id == 2 { return nil } // 模拟失败
return "OK \(id)"
}
当任务失败时,返回nil,外层过滤nil。
3、错误触发并取消其余任务
enum FetchError: Error { case failed }
func riskyTask(_ id: Int) async throws -> Int {
if id == 2 { throw FetchError.failed }
return id * 10
}
func runThrowingGroup() async throws -> [Int] {
try await withThrowingTaskGroup(of: Int.self) { group in
for i in 1...3 {
group.addTask {
try await riskyTask(i)
}
}
var results: [Int] = []
do {
for try await value in group {
results.append(value)
}
} catch {
group.cancelAll() // 失败后取消剩余任务
throw error
}
return results
}
}
当任务发生报错后,使用cancelAll()取消剩余任务。
async let 和 TaskGroup的区别
async let 和 TaskGroup 都能实现并发执行多个任务,但是在设计目的、能力范围和使用场景上有明显区别。
1、不固定数量任务:async let不支持且不能循环,TaskGroup支持并且可以循环。
// async let 不支持动态循环
for url in urls {
async let image = downloadImage(from: url) // 错误
}
// TaskGroup 支持
for url in urls {
group.addTask {
try await downloadImage(from: url)
}
}
2、任务控制能力:async let不支持添加任务时做逻辑判断,不支持添加任务时动态设置参数,不支持在子任务里处理错误,TaskGroup具有任务控制能力,可以添加任务并判断、记录和处理异常。
3、错误处理:async let支持错误传播,但不能捕获局部错误。当整个await抛出错误时,不会处理其他任务。TaskGroup支持错误传播和捕获局部错误。还可以处理每个任务抛出的错误,灵活控制。
// async let:某个任务失败,整体 throw,没法控制哪个失败
async let a = try mayFail1()
async let b = try mayFail2()
let result = try await [a, b]
// TaskGroup:每个任务都可单独 try-catch
group.addTask {
do {
return try await mayFail()
} catch {
print("一个任务失败了,但继续")
return defaultValue
}
}
4、性能优化:async let在声明后立即开始,TaskGroup在addTask时立即开始。
async let a = fetchData1() // 执行并启动任务
async let b = fetchData2() // 执行并启动任务
async let c = fetchData3() // 执行并启动任务
上面这三行代码,每一行一执行就启动任务,而不是等到 await 的时候才开始执行。
三个任务是并发执行的,只是稍后再取结果。
try await withThrowingTaskGroup(of: Int.self) { group in
group.addTask { try await fetchData1() }
group.addTask { try await fetchData2() }
group.addTask { try await fetchData3() }
}
这里任务在调用group.addTask { … } 的时候就立刻开始执行了。
两者在任务开始时机上是一样的,都是定义时立刻启动。
区别主要是:
async let 是语法上的声明。
TaskGroup 可以在任意位置调用 addTask,甚至放进 for 循环里动态控制。
5、返回结构顺序:async let按声明顺序固定,TaskGroup按任务完成顺序,如果需要保留顺序,需要自己记录index。
async let a = fetch1()
async let b = fetch2()
async let c = fetch3()
let result1 = try await a // 对应 fetch1()
let result2 = try await b // 对应 fetch2()
let result3 = try await c // 对应 fetch3()
顺序始终一致,结果也一一对应。
try await withThrowingTaskGroup(of: String.self) { group in
group.addTask { try await fetch("A") }
group.addTask { try await fetch("B") }
group.addTask { try await fetch("C") }
var results: [String] = []
for try await result in group {
results.append(result)
}
return results
}
这里的 result 是哪个任务先完成就先返回,不是 addTask 的顺序。
比如写了 A→B→C,结果可能是 B 最快返回,顺序就变成:
["B 的结果", "A 的结果", "C 的结果"]
如果想要在TaskGroup中保证顺序,可以在addTask里手动标记顺序:
group.addTask {
let result = try await fetch("A")
return (index: 0, value: result)
}
然后最后排序:
results.sort { $0.index < $1.index }
注意事项
async let 不能放在循环或条件语句中(因为数量必须在编译时固定):
// 错误:不能在 for 中用 async let
for url in urls {
async let img = loadImage(from: url)
}
TaskGroup 不返回顺序结果,结果的顺序由任务完成顺序决定,如果需要有序结果,需额外处理。
