如何在非异步方法中实现异步任务串行执行,解决多远程通知触发时同步任务顺序混乱问题
我太懂你现在的困扰了——多个远程通知触发后,原本期望串行执行的异步任务全挤在一起跑,日志顺序乱得一塌糊涂,完全不是你想要的逐个完整执行的效果。
咱们先搞清楚问题出在哪:你在同步的application(_:didReceiveRemoteNotification:)方法里,每次都新建一个Task去调用fetchAllChangesAsync,而这些Task默认会在全局并发队列上执行,相当于多个任务同时开工,自然就会出现start fetching、start saving穿插的情况。
要解决这个问题,核心就是把这些异步任务强制排成串行队列,一个执行完再启动下一个,这里有两个实用方案,推荐优先用更贴合Swift现代并发风格的那个:
方案一:用Actor实现串行执行(推荐)
Actor是Swift 5.5+引入的并发原语,它的核心特性就是所有方法都是串行执行的——不管有多少个异步调用请求,都会排队等待上一个任务完成后再执行。
你只需要把CloudAssistant的类声明改成actor就行,其他逻辑几乎不用动:
@MainActor actor CloudAssistant: ObservableObject { static let shared = CloudAssistant() func fetchAllChangesAsync() async throws { defer { // 保留你原来的defer逻辑 } print("start fetching") // 这里的CloudKit异步请求代码不变 // 这里的Core Data更新await代码不变 print("start saving") print("end saving") } }
然后在ApplicationDelegate里的调用代码不用改,还是原来的样子:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) { print("✅fetching changes") Task { try await CloudAssistant.shared.fetchAllChangesAsync() } }
这样修改后,即使多个通知触发,每个fetchAllChangesAsync调用都会在Actor的串行执行队列里排队,严格按照「start fetching → start saving → end saving」的完整流程逐个执行,完全符合你想要的日志顺序。
方案二:自定义串行任务队列(兼容旧版本Swift)
如果你的项目还没升级到支持Actor的Swift版本,也可以用DispatchQueue创建一个自定义的串行队列,把异步任务包装在队列里执行:
@MainActor class CloudAssistant: ObservableObject { static let shared = CloudAssistant() // 创建一个专属串行队列 private let serialQueue = DispatchQueue(label: "com.yourname.cloudassistant.serial") func fetchAllChangesAsync() async throws { // 用withCheckedThrowingContinuation把串行队列的操作桥接到async/await try await withCheckedThrowingContinuation { continuation in serialQueue.async { [weak self] in guard let self = self else { continuation.resume(throwing: NSError(domain: "CloudAssistant", code: -1, userInfo: [NSLocalizedDescriptionKey: "实例已释放"])) return } Task { do { defer { // 保留原来的defer逻辑 } print("start fetching") // 这里的CloudKit异步请求 // 这里的Core Data更新await方法 print("start saving") print("end saving") continuation.resume() } catch { continuation.resume(throwing: error) } } } } } }
不过这种方法需要手动处理async/await和DispatchQueue的桥接,代码相对繁琐,还是更推荐用Actor的方案,更符合Swift现代并发的设计思路。
不管用哪种方案,最终都能实现你想要的执行顺序:
✅fetching changes ✅fetching changes ✅fetching changes start fetching start saving end saving start fetching start saving end saving start fetching start saving end saving
内容来源于stack exchange

