Swift: DispatchQueue 是否始终为串行?全局队列异步写入顺序问题咨询
问题解答
顺序问题结论
你提到的场景下确实可能出现数据B的UserDefaults write操作早于数据A执行,原因和GCD队列的特性直接相关:
- GCD的队列分为串行队列、并行队列两类:
- 串行队列同一时间只会执行一个任务,严格按任务提交的先后顺序执行
- 并行队列会调度多个线程同时执行队列内的任务,任务执行顺序、完成时间都不受提交顺序约束
- 你代码中用到的
DispatchQueue.global()拿到的是系统提供的全局并行队列,不管是.userInitiated还是.utility级别的全局队列,都是并行执行的。
你先后收到数据A、B并提交任务后,两个userInitiated级别的处理任务会并行执行,如果B的some work 2耗时更短,就会先进入写入步骤;哪怕A的处理先完成,提交到utility全局并行队列的写入任务依然是并行调度,无法保证A的写入早于B执行,最终就会出现UserDefaults存储旧值的异常。
优化方案
结合你的场景(新数据会让旧处理结果失效),有两种常用的适配方案:
方案1:私有串行队列保证执行顺序
创建一个私有的串行队列统一处理所有数据处理、写入逻辑,天然保证任务严格按接收顺序执行,不会出现乱序问题:
// 全局声明私有串行队列,标识建议用反向域名格式避免冲突,QoS按需设置 private let dataProcessQueue = DispatchQueue(label: "com.yourapp.dataProcessQueue", qos: .userInitiated) func onReceivedData(d: Data) { // 主线程调用 // 如果some work 1是轻量UI相关操作可以留在主线程,耗时操作建议扔到串行队列避免卡主线程 // some work 1 dataProcessQueue.async { // some work 2 if saveToFile { // 当前已经在私有的串行队列,直接执行写入即可,不需要再提交到其他队列 // UserDefaults write } } }
方案2:取消旧任务避免无效计算
如果旧数据的处理在新数据到达后已经没有执行价值,可以用带取消能力的任务管理机制,每次接收到新数据就取消上一次未完成的处理,既节省算力也能保证最终写入的是最新数据的结果:
// 全局持有当前处理任务的引用 private var currentProcessTask: Task<Void, Never>? func onReceivedData(d: Data) { // 接收到新数据直接取消上一次未完成的处理 currentProcessTask?.cancel() currentProcessTask = Task(priority: .userInitiated) { // some work 2 // 处理过程中可添加取消判断,提前终止无效任务 guard !Task.isCancelled else { return } if saveToFile { // UserDefaults write } } }
内容的提问来源于stack exchange,提问作者user2161301
相关产品推荐
相关产品推荐

