思维导图(先保存,再阅读)
vbnet
Swift Concurrency
├─ Structured(结构化并发,有父有子,有纪律)
│ ├─ async let → 静态并发
│ └─ TaskGroup → 动态并发
│ 三大规则 EGG
│ ├─ E Error 传播:出作用域即 取消+等待
│ ├─ G Group 完成:父必等子
│ └─ G Group 取消:父取消→子取消
│ 特征
│ ├─ 生命周期=作用域
│ ├─ 隐式 await(作用域结束)
│ └─ 继承优先级 & TaskLocal,**不继承 actor**
└─ Unstructured(无父,野生)
├─ Task { } → 继承上下文
├─ Task.detached { } → 啥也不继承
└─ 无 EGG 规则,一切靠自己
为什么分"结构化"与"非结构化"
维度 | Structured | Unstructured |
---|---|---|
能否成为子任务 | ✅ | ❌(只能是根) |
能否成为父任务 | ✅ | ✅(再开 structured 子任务) |
生命周期 | 绑定作用域 | 绑定引用 |
规则 EGG | 自动生效 | 全无效 |
典型 API | async let / TaskGroup |
Task {} / Task.detached {} |
Structured 任务两大形态
async let ------ 静态并发
swift
func fetchData() async {
async let first = fetchPart1() // 立即启动子任务
async let second = fetchPart2()
async let third = fetchPart3()
let result = await (first, second, third) // 隐式等全部完成
print(result)
}
// 离开作用域前,**所有子任务必须完成**,否则父任务不会完成
注意:
- 顺序:
await
不影响并发,三个子任务同时飞。 - 只要作用域还在,就保证等;提前
return
/throw
也会自动取消+等待其余子任务。
TaskGroup ------ 动态并发
swift
func fetchData(count: Int) async {
await withTaskGroup(of: String.self) { group in
for i in 0..<count {
group.addTask { // 动态添加子任务
await fetchPart(i)
}
}
for await result in group { // 谁跑完谁先处理
print(result)
}
} // 此处**隐式等待**所有子任务
}
优点:
- 数量运行时决定
- fail-fast:任一子任务抛错,其余子任务立即取消并传播第一个错误
- 支持
group.cancelAll()
手动取消
Unstructured 任务:两座"根"
常规 Task { } ------ 继承上下文
swift
enum TaskLocalStorage {
@TaskLocal static var requestID: String?
}
Task { // 继承
print(Task.currentPriority) // 继承调用方优先级
print(TaskLocalStorage.requestID) // 继承 TaskLocal
// 若在外层 @MainActor,也继承 executor,但内部函数自己的 actor 隔离仍生效
}
Task.detached { } ------ 啥也不继承
swift
Task.detached {
// 优先级 = .medium(默认)
// 无 TaskLocal
// 一定跑在**全局并发线程池**,不会上主线程
}
使用场景:
- 真正"后台孤岛"计算
- 但99 % 场景用常规
Task { }
+ 函数自身nonisolated
即可,detached 是最后 resort
三大铁律 EGG(只适用于 Structured)
E - Error 传播规则
定义:作用域因抛错而提前退出时,所有子任务自动取消 + 等待。
swift
func fast() async throws { ... throw TestError() }
func slow() async throws { ... }
func parent() async throws {
async let f = fast() // 5 s 后抛错
async let s = slow() // 10 s 后完成
try await (f, s) // fast 先抛,slow 被**自动取消并等待**
}
// 离开作用域前,**所有子任务必须完成或被取消**,错误才继续向上抛
对比 Unstructured:
swift
let root = Task {
Task { try await fast() } // 无父子关系
Task { try await slow() } // **不会被自动取消**
}
root.cancel() // 两个嵌套 Task 仍跑完
G - Group 完成规则
定义:父任务必须等所有子任务完成后才能完成。
swift
let parent = Task {
async let a = work() // 10 s
async let b = work() // 10 s
_ = await (a, b)
}
await parent.value // **20 s 后才打印**
print("parent completes")
Unstructured 版本:
swift
let root = Task {
Task { await work() } // 10 s
}
await root.value // **立刻打印**,嵌套 Task 继续跑
print("root completes") // 出现在 work 之前
G - Group 取消规则
定义:父任务被取消 → 所有子任务自动取消(协作式)。
swift
let parent = Task {
async let a = longWork() // 10 s
async let b = longWork() // 10 s
_ = await (a, b)
}
parent.cancel() // a & b 收到 `CancellationError`
await parent.value
Unstructured 再次失效:
swift
let root = Task {
Task { await longWork() } // **收不到取消**
}
root.cancel()
上下文继承差异速查
继承项 | Structured 子任务 | Task {} | Task.detached { } |
---|---|---|---|
优先级 | ✅ | ✅ | ❌(默认 .medium) |
TaskLocal | ✅ | ✅ | ❌ |
Actor 隔离 | ❌(永远不继承,跑全局并发池) | ✅(继承调用方 executor) | ❌(全局池) |
易错点:
async let
/group.addTask
不会把代码钉在 @MainActor
,即使外层是主线程;
内部函数自己的isolate决定最终 executor。
什么时候用哪种任务?一张表搞定
需求 | 选型 |
---|---|
静态并发(固定数量) | async let |
动态并发 & fail-fast | TaskGroup |
同步→异步 | Task { } |
后台"孤岛"计算,不继承任何东西 | Task.detached { } (最后 resort) |
Fire-and-forget(不 await) | Task { } 但记得手动管理生命周期 |
实战模板:网络层"结构化"封装
swift
/// 并发下载多张图片,任一失败立即取消其余,返回首张
@MainActor
func fetchFirstImage(urls: [URL]) async throws -> UIImage {
try await withThrowingTaskGroup(of: UIImage.self) { group in
for url in urls {
group.addTask {
let (data, _) = try await URLSession.shared.data(from: url)
guard let image = UIImage(data: data) else {
throw URLError(.badURL)
}
return image
}
}
// 只要第一张成功,其余自动取消
for try await image in group {
return image // 提前返回 → 其余任务被取消
}
throw URLError(.badServerResponse)
}
}
特点:
- 结构化保证"失败即取消"
- 离开作用域自动等待,不会泄漏任务
常见踩坑清单
踩坑 | 正确做法 |
---|---|
async let 忘了 await |
作用域结束自动等,但返回值别丢 |
Task { } 里开 async let 就当子任务 |
❌ 无父子关系,EGG 规则全失效 |
想取消单个 async let |
必须取消整个父任务;用 TaskGroup + cancelAll() 更细 |
Task.detached 当成"性能加速器" |
99 % 用常规 Task { } + 函数自己的 nonisolated 即可 |
一句话总结
Structured = 有爹管:生命周期、取消、错误、完成,作用域帮你兜底。
Unstructured = 野生根:一切自己 await、自己 cancel、自己管理。
记住 EGG 只给"有爹"的任务吃,野生任务饿了自己煮。