Swift 结构化并发 6 条铁律 —— 一张图 + 一套模板,让 `async let` / `TaskGroup` / `Task {}` 不再踩坑

思维导图(先保存,再阅读)

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 只给"有爹"的任务吃,野生任务饿了自己煮。

相关推荐
报错小能手7 小时前
ios开发方向——swift错误处理:do/try/catch、Result、throws
开发语言·学习·ios·swift
小夏子_riotous10 小时前
openstack的使用——5. Swift服务的基本使用
linux·运维·开发语言·分布式·云计算·openstack·swift
mCell14 小时前
MacOS 下实现 AI 操控电脑(Computer Use)的思考
macos·agent·swift
用户794572239541314 小时前
【DGCharts】iOS 图表渲染事实标准——8 种图表类型、高度可定制,3 行代码画出一条折线
swiftui·swift
chaoguo12341 天前
Any metadata 的内存布局
swift·metadata·value witness table
tangweiguo030519872 天前
SwiftUI布局完全指南:从入门到精通
ios·swift
用户79457223954133 天前
【RxSwift】Swift 版 ReactiveX,响应式编程优雅处理异步事件流
swift·rxswift
战族狼魂3 天前
XCode 发起视频 和 收到视频通话邀请实现双语功能 中文和俄语
swift
UXbot3 天前
2026年AI全链路产品开发工具对比:5款从创意到上线一站式平台深度解析
前端·ui·kotlin·软件构建·swift·原型模式
报错小能手3 天前
ios开发方向——swift并发进阶核心 @MainActor 与 DispatchQueue.main 解析
开发语言·ios·swift