TaskGroup 和 async let 都是用于并发并行执行多个异步任务的工具,但它们各自适用于不同的场景。
async let:并发启动多个已知数量的异步任务,适合数量固定、结构清晰的并发。
TaskGroup:动态创建多个异步任务并等待全部完成,适合数量不定、动态生成的任务。
async let
并发执行多个已知任务。
async let image1 = loadImage(from: "url1")
async let image2 = loadImage(from: "url2")
async let image3 = loadImage(from: "url3")
let result = try await [image1, image2, image3]
另一种写法:
async let image1 = loadImage(from: "url1")
async let image2 = loadImage(from: "url2")
async let image3 = loadImage(from: "url3")
let result1 = try await image1
let result2 = try await image2
let result3 = try await image3
特点:
启动三个异步任务并发执行,而不是顺序。
最终使用 await 等待所有任务结果。
适合任务数量确定且类型一致(如并发加载多个图片)。
注意:不能用for循环(数量必须提前写死。)
TaskGroup
动态并发多个任务。
func fetchAllImages(urls: [String]) async throws -> [UIImage] {
try await withThrowingTaskGroup(of: UIImage.self) { group in
for url in urls {
group.addTask {
try await loadImage(from: url)
}
}
var results: [UIImage] = []
for try await image in group {
results.append(image)
}
return results
}
}
特点:
可在运行时动态添加任务。
自动并发执行。
支持错误处理(withThrowingTaskGroup)。
比 async let 更灵活,适合处理不定数量的异步任务(如循环、分页、动态生成内容)。
代码解析:
1、withThrowingTaskGroup(…):创建一个任务组(所有任务一起并发执行,支持抛错)。
2、for url in urls:动态遍历 URL,可以是 3 个、10 个、100 个都行。
3、group.addTask { … }:每次调用 addTask,就创建一个任务。任务立即启动,最终会返回一个UIImage。
4、for try await image in group:一个个收集结果,哪个任务先完成,就先得到哪个结果,其中image是任务返回的值。
5、results.append(image):把下载好的图片收集到数组中。
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 不返回顺序结果,结果的顺序由任务完成顺序决定,如果需要有序结果,需额外处理。