一、什么是 async let
?
async let
是 Swift 提供的一种结构化并发语法糖,用于并发地启动多个子任务,并延迟等待其结果。
✅ 基本用法
swift
func fetchData() async {
async let first = fetchPart1()
async let second = fetchPart2()
async let third = fetchPart3()
let result = await (first, second, third)
print(result)
}
- 每个
async let
会创建一个子任务(child task)。 - 子任务立即开始执行,不会阻塞当前任务。
- 使用
await
获取结果时,按顺序等待(从左到右)。
✅ 支持任意表达式
swift
async let number = 123
async let str = "Hello"
实际上,Swift 会将这些表达式包装为
ChildTask
,并在后台并发执行。
二、什么是 TaskGroup?
TaskGroup
是 Swift 提供的动态并发任务创建机制,适合任务数量在运行时决定的场景。
✅ 示例:动态创建任务
swift
func fetchData(count: Int) async {
var results = [String]()
await withTaskGroup(of: String.self) { group in
for index in 0..<count {
group.addTask {
await self.fetchPart(index)
}
}
for await result in group {
results.append(result)
}
}
print(results)
}
addTask
动态添加子任务。for await
按完成顺序处理结果(谁先完成谁先处理)。- 也可以使用
group.next()
手动迭代。
三、生命周期机制对比
特性 | async let |
TaskGroup |
---|---|---|
生命周期作用域 | 当前局部作用域(函数、do块等) | withTaskGroup 闭包 |
正常退出时 | 自动取消并等待所有子任务 | 自动等待所有子任务(不取消) |
异常退出时 | 自动取消并等待所有子任务 | 自动取消并等待所有子任务 |
是否支持动态任务数 | ❌ 不支持 | ✅ 支持 |
是否按完成顺序处理结果 | ❌ 按声明顺序 await |
✅ 按完成顺序 |
四、错误传播机制详解
✅ async let
的错误传播
swift
async let f = fast()
async let s = slow()
do {
try await (f, s)
} catch {
print("Caught error: \(error)")
}
- 错误传播顺序取决于
await
的顺序。 - 如果
fast()
先抛出错误,slow()
会被隐式取消并等待。 - 如果
slow()
先完成但fast()
先抛出错误,slow()
的错误不会被捕获。
✅ TaskGroup 的错误传播
swift
try await withThrowingTaskGroup(of: Void.self) { group in
group.addTask { try await fast() }
group.addTask { try await slow() }
for try await _ in group {
// 处理结果
}
}
- 使用
for try await
时,第一个抛出的错误会立即传播。 - 其余任务会被取消并等待。
- 错误传播顺序与任务完成顺序无关,谁先抛谁传播。
五、实战建议:如何选择?
场景 | 推荐方式 | 原因 |
---|---|---|
任务数量固定 | async let |
简洁、语法糖优雅 |
任务数量动态 | TaskGroup |
支持运行时添加任务 |
需要"fail fast" | TaskGroup |
错误传播更及时、可控 |
需要按顺序处理结果 | async let |
可控制 await 顺序 |
需要按完成顺序处理结果 | TaskGroup |
AsyncSequence 自动支持 |
六、常见误区与注意事项
⚠️ 1. async let
不支持逃逸闭包
swift
async let result = someAsyncFunc()
DispatchQueue.main.async {
// ❌ 不能使用 result,生命周期已结束
}
⚠️ 2. 不 await
也会等待完成
swift
async let f = slowTask()
print("End") // 仍会等待 f 完成后再退出作用域
⚠️ 3. 不支持"真正的 fire-and-forget"
- 结构化任务总是会被等待。
- 若需真正"放飞自我",请使用非结构化 Task:
swift
Task.detached {
await someAsyncFunc()
}
七、总结:一句话记住差异
async let
是固定并发任务的语法糖,生命周期绑定作用域,错误传播顺序依赖 await
顺序;
TaskGroup
是动态并发任务的管理器,生命周期绑定闭包,错误传播顺序更及时、可控。