Swift 5.5搭配Core Data使用withTaskGroup偶现崩溃如何解决?
问题结论
这不是Swift测试版的bug,是代码违反了Core Data的线程安全规则导致的偶现崩溃。
崩溃根本原因
Core Data的NSManagedObjectContext(下文简称moc)和托管对象本身不是线程安全的,代码犯了两个核心错误:
- TaskGroup添加的所有子任务是并发执行的,会被调度到不同线程运行,直接在所有子任务里共享同一个moc实例,跨线程操作moc完全不符合Core Data的并发约束。
- 多线程同时读写同一个moc会触发不可预期的竞争问题,你遇到的尝试插入nil的崩溃就是典型表现:多线程并发操作下某一步查询/创建返回了空对象,后续Core Data内部处理变更通知时尝试把空对象插入集合触发异常,这也是崩溃偶现的原因,只有线程竞争刚好命中临界区才会触发。
修复方案
方案1(推荐,保留并发能力)
每个并发子任务单独创建私有队列类型的子上下文,所有Core Data操作只在当前任务的私有子上下文上执行,全部操作完成后再统一合并到父上下文:
await withTaskGroup(of: OnboardingMeter.self) { group in for (number, codableAddress) in meters { group.addTask { // 每个任务单独创建私有子上下文 let privateMoc = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) privateMoc.parent = moc // 绑定到原有的父上下文 print("The following statement sometimes fails to return an onboarding meter, resulting in a crash") let onboardingMeter = await OnboardingMeter.fromMeterDict(number, address: codableAddress, in: privateMoc) // 保存私有上下文把变更合并到父上下文 try? privateMoc.save() print("onboardingMeter:", onboardingMeter) // 注意不要把私有上下文的托管对象直接传到外部,需要的话用objectID在父上下文查询获取 return onboardingMeter } } for await meter in group { onboardingMeters.append(meter) } }
同时要保证所有moc的查询、托管对象属性修改操作都包在对应moc的perform闭包内执行,现有的findOrCreateInstance方法如果没有把查询逻辑放在perform里,也需要同步调整,保证所有moc操作都在对应上下文的专属队列执行。
方案2(临时快速修复,失去并发意义)
如果不需要并发处理,直接去掉TaskGroup改成串行遍历处理meters数组,避免多线程同时操作moc即可。
内容的提问来源于stack exchange,提问作者Ivan C Myrvold
相关产品推荐
相关产品推荐

