SwiftUI中如何用DispatchQueue/DispatchGroup串行执行异步函数
需求明确
你有两个独立类:一个带async update()(用于更新服务器数据),另一个是@StateObject的Fetcher类带fetch();要求视图触发fetch()时,必须等正在运行的update()完成后再执行,且两个类实例不能直接依赖、不能用回调。
各方案踩坑分析
1. DispatchQueue
别折腾这个了——GCD队列和Swift的async/await不是一套模型。直接在DispatchQueue.main.async里调用异步函数会报错,包个Task又会脱离队列的串行控制(Task会走Swift自己的并发调度器);用sync调用异步函数更是直接触发运行时错误,因为sync会阻塞线程,而异步函数需要挂起恢复,根本不兼容。
结论:DispatchQueue搞不定这个需求,pass。
2. DispatchGroup
要让两个类用同一个DispatchGroup,必须把group实例传给它们——这直接破坏了你要的“类独立”要求。而且DispatchGroup是给GCD设计的,适配async/await得手动写enter()/leave(),稍不注意就漏写,容易出bug。
结论:需要共享实例,不符合需求,pass。
3. OperationQueue
和DispatchGroup一样,得让两个类共用同一个队列或者操作实例,同样破坏独立性。而且Operation处理异步任务还要手动管理isFinished状态,结合async/await代码会又臭又长,完全没必要。
结论:同样需要共享实例,pass。
推荐方案:用Swift Actor做串行调度
Actor是Swift并发模型里的串行执行单元,完美适配async/await,而且不需要两个类直接交互,只需要共享一个调度用的Actor就行——类本身还是完全独立的。
原理
Actor的所有方法调用都会自动排队,串行执行。我们搞个专门的TaskScheduler Actor,让update()和fetch()都通过这个Actor来执行,天然保证顺序。
可运行的最小示例
import SwiftUI // 专门的任务调度Actor,负责串行执行所有任务 actor TaskScheduler { func run<T>(_ job: @Sendable async () -> T) async -> T { await job() } } // 带update方法的类 class Updater { private let scheduler: TaskScheduler init(scheduler: TaskScheduler) { self.scheduler = scheduler } func update() async { await scheduler.run { print("1") // 模拟2秒的长任务 try? await Task.sleep(nanoseconds: 2_000_000_000) print("2") } } } // @StateObject的Fetcher类 class Fetcher: ObservableObject { private let scheduler: TaskScheduler init(scheduler: TaskScheduler) { self.scheduler = scheduler } func fetch() async { await scheduler.run { print("these would be some results") } } } struct ContentView: View { @StateObject private var fetcher: Fetcher private let updater: Updater init() { let scheduler = TaskScheduler() // 两个类共用同一个调度Actor,但互相之间没有直接依赖 self.fetcher = Fetcher(scheduler: scheduler) self.updater = Updater(scheduler: scheduler) } var body: some View { VStack(spacing: 20) { Button("start long task") { Task { await updater.update() } } Button("fetch") { Task { await fetcher.fetch() } } } .padding() } }
效果
点击“start long task”后立刻点“fetch”,控制台会严格按顺序输出:
1 2 these would be some results
为什么这个方案靠谱
- 类完全独立:Updater和Fetcher只持有同一个调度Actor的引用,互相之间没有任何直接调用或依赖
- 串行有保障:Actor的方法调用自动排队,不管哪个类发起的任务,都会按触发顺序执行
- 适配Swift并发:完全用
async/await和Actor,不用混GCD,代码简洁还不容易出错
方案对比表
| 方案 | 是否需要共享实例 | 适配async/await | 代码复杂度 | 符合需求? |
|---|---|---|---|---|
| DispatchQueue | 否,但模型不兼容 | ❌ | 中 | ❌ |
| DispatchGroup | 是 | ❌ | 高 | ❌ |
| OperationQueue | 是 | ❌ | 高 | ❌ |
| Actor调度 | 仅共享调度Actor,类独立 | ✅ | 低 | ✅ |
内容的提问来源于stack exchange,提问作者ambassador

