URLSession异步调用并发问题:上传服务重复执行与死锁排查
解决上传服务并发重复调用与死锁问题
问题根源分析
- 每次创建
UploadService实例都会启动独立异步任务执行上传,多实例同时运行导致后端重复接收请求。 - 使用
NSLock时,async/await的异步上下文会切换执行线程:锁在await阶段持续被持有,后续任务阻塞等待锁资源;当前任务在await完成后可能切换到其他线程,导致锁无法正常释放或执行流程被阻塞,最终无法进入success/failure分支。
解决方案:用Actor实现并发安全控制
Swift的Actor是原生并发安全原语,能自动保证内部方法串行执行,搭配单例设计可彻底解决重复调用与死锁问题。
修改后的代码
// 用Actor保证内部逻辑串行执行,天然实现并发安全 actor UploadService { // 全局唯一单例,避免外部重复创建实例 static let shared = UploadService() // 私有化初始化方法,禁止外部实例化 private init() {} func uploadData() async { let context = PersistentContainer.shared.newBackgroundContext() // 确保Core Data操作在其专属线程执行,避免线程安全问题 context.performAndWait { guard let uploadedThing = Upload.coreDataFetch(in: context) else { return } Task { // 注意:此处原代码存在命名冲突,建议将网络请求类重命名为UploadRequest let request = UploadRequest(configuration: networkConfiguration) let result = await request.uploadList(uploadedThing) switch result { case .success: print("success") case .failure(let error as NSError): print("error happened: \(error.localizedDescription)") } } } } } class UploadExtension { func createUploadService() { for i in 0...4 { // 调用单例的上传方法,即使循环多次也只会串行执行任务 Task { await UploadService.shared.uploadData() } } } }
关键改动说明
- Actor化改造:将
UploadService声明为Actor,Swift自动保证其所有方法串行执行,无需手动加锁,从根源避免并发冲突。 - 单例设计:通过私有化初始化+全局单例,确保外部无法创建多个实例,彻底消除多实例并发触发上传的问题。
- Core Data线程安全:使用
context.performAndWait包裹Core Data查询操作,确保数据访问在其专属线程执行,规避线程安全风险。 - 调用逻辑调整:循环中直接调用单例的
uploadData方法,多次调用会被Actor串行处理,不会重复触发后端请求。
额外注意事项
- 原代码中
UploadService(configuration: networkConfiguration)存在命名冲突,建议将负责网络请求的类重命名(如UploadRequest),避免与Actor类混淆。 - 若需支持上传任务取消,可在Actor中存储当前
Task实例,新增取消方法实现任务终止逻辑。 - 如需重试机制,可在
failure分支中添加重试逻辑,借助Actor的串行特性保证重试任务有序执行。
内容的提问来源于stack exchange,提问作者atalayasa
相关产品推荐
相关产品推荐

