You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 10:27:51