SwiftUI异步函数并发问题:如何实现Task同步或任务取消?
解决Swift异步函数并发执行导致的CoreData数据覆盖问题
你遇到的核心问题是getRecentMovies被两处触发源同时调用,导致异步任务并发执行,进而引发CoreData读写冲突、数据被覆盖的问题。下面提供两种针对性的解决方案,优先推荐贴合Swift并发模型的任务追踪方案。
方案一:追踪运行中任务,避免重复执行
在ViewModel中维护一个可选的Task变量,用来追踪当前是否有正在执行的getRecentMovies任务,根据业务需求选择“取消旧任务启动新任务”或“忽略新任务等待旧任务完成”的逻辑。
实现方式1:取消旧任务,启动新任务
适合需要优先响应最新触发(比如用户刚打开APP时收到推送,优先用推送触发的请求结果)的场景:
class MovieViewModel: ObservableObject { private var ongoingFetchTask: Task<Void, Error>? func getRecentMovies() { // 取消正在运行的旧任务 ongoingFetchTask?.cancel() ongoingFetchTask = Task { [weak self] in do { defer { self?.ongoingFetchTask = nil } // 任务完成后清空追踪变量 // 发起网络请求 let apiResponse = try await self?.fetchFromAPI() ?? [] // 检查任务是否已取消,避免无效执行 guard !Task.isCancelled else { return } // 读取CoreData本地数据 let localData = try await self?.readFromCoreData() ?? [] guard !Task.isCancelled else { return } // 合并API数据与本地数据 let updatedData = merge(apiData: apiResponse, localData: localData) guard !Task.isCancelled else { return } // 保存到CoreData try await self?.saveToCoreData(updatedData) } catch { // 忽略取消错误,处理其他异常 if !(error is CancellationError) { print("获取电影数据失败:\(error)") } } } } // 以下是业务逻辑辅助方法示例 private func fetchFromAPI() async throws -> [Movie] { // 网络请求实现 } private func readFromCoreData() async throws -> [Movie] { // CoreData读取逻辑 } private func merge(apiData: [Movie], localData: [Movie]) -> [Movie] { // 数据合并逻辑(比如去重、更新字段) } private func saveToCoreData(_ data: [Movie]) async throws { // CoreData保存逻辑 } }
实现方式2:忽略新任务,等待旧任务完成
适合需要保证先触发的任务执行完成,避免频繁取消任务的场景:
func getRecentMovies() { // 如果已有任务在运行,直接返回 guard ongoingFetchTask == nil else { return } ongoingFetchTask = Task { [weak self] in do { defer { self?.ongoingFetchTask = nil } // 业务逻辑同上述示例 let apiResponse = try await self?.fetchFromAPI() ?? [] let localData = try await self?.readFromCoreData() ?? [] let updatedData = merge(apiData: apiResponse, localData: localData) try await self?.saveToCoreData(updatedData) } catch { print("获取电影数据失败:\(error)") } } }
这个方案完全贴合Swift的异步并发模型,无需依赖传统锁机制,避免了线程阻塞风险,是优先推荐的解决方案。
方案二:使用NSLock实现串行执行(不推荐)
如果一定要用锁机制,需要注意不要在await挂起点持有锁,否则会阻塞线程。正确的做法是仅在访问CoreData共享资源时加锁:
class MovieViewModel: ObservableObject { private let dataLock = NSLock() // 假设你有CoreData上下文实例 private let coreDataContext: NSManagedObjectContext func getRecentMovies() { Task { do { // 网络请求无需加锁 let apiResponse = try await fetchFromAPI() // 读取CoreData时加锁 dataLock.lock() let localData = try coreDataContext.fetch(Movie.fetchRequest()) dataLock.unlock() let updatedData = merge(apiData: apiResponse, localData: localData) // 保存CoreData时加锁 dataLock.lock() // 将updatedData写入coreDataContext try coreDataContext.save() dataLock.unlock() } catch { print("获取电影数据失败:\(error)") } } } }
这种方式虽然能实现串行执行,但不符合Swift并发编程的最佳实践,锁机制在异步环境下容易引发线程阻塞或死锁问题,因此仅作为备选方案。
内容的提问来源于stack exchange,提问作者mikemgg123
相关产品推荐
相关产品推荐

