当多个核心数据插入使用 performBackgroundTask 调度到 NSPersistentContainer



我正在试验新的核心数据API NSPersistentContainer,并且认为内部排队机制会阻止写入事务并发评估,如此堆栈溢出答案NSPersistentContainer并发保存到核心数据中所述

许多专业人士长期以来(甚至在 NSPersistentContainer 这样做之前)一直在处理这个问题的方式是有一个操作队列来排队写入,这样一次只有一个写入,并且在主线程上还有另一个上下文仅用于读取。这样,您就不会遇到任何合并冲突。(有关此设置的精彩解释,请参阅 https://vimeo.com/89370886,现在是NSPersistentContainer在内部所做的)。 当您调用 performBackgroundTask 时,持久容器会将阻塞排队到内部串行队列中。这可确保没有合并冲突。

但是,如果我在每次迭代中使用performBackgroundTask在紧密循环中插入多个共享关系目标的实体,则在保存上下文时会出现NSMergeConflict错误:

let bundlePath = Bundle.main.resourceURL!
let directoryEnumerator = FileManager.default.enumerator(at: bundlePath, includingPropertiesForKeys: [URLResourceKey.isDirectoryKey, URLResourceKey.nameKey])
while let url = directoryEnumerator?.nextObject() as? URL {
if url.pathExtension == "jpeg" {
let imageData = try! Data(contentsOf: url)
DataManager.persistentContainer.performBackgroundTask { (context) in
//          context.mergePolicy = NSMergePolicy.overwrite
let new = Photo(context: context)
new.name = url.lastPathComponent
new.data = imageData as NSData
let corresponding = try! context.existingObject(with: DataManager.rootFolder.objectID) as! Folder
new.parent = corresponding
try! context.save()
}
}

我在 github 上发布了一个示例项目来演示这个问题: https://github.com/MaximeBoulat/NSPersistentContainer_Merge_Conflict

崩溃似乎是因为多个实体同时为同一父实体设置其"父"关系,这会导致父实体的"子"关系在并发更新之间不同步。

即使我将传入上下文的.automaticallyMergesChangesFromParent属性设置为 true,也会发生这种情况。我可以通过定义传入上下文的合并策略来防止崩溃,但这不是可接受的解决方案。

有没有办法配置 NSPersistentContainer 以正确序列化使用performBackgroundTaskAPI 调度的更新。还是我缺少导致这些更新相互冲突的东西?或者苹果是否向NSPersistentContainer堆栈提供了这样的期望:在评估传递到performBackgroundTask逻辑时遇到的任何冲突都应该是致命的,或者被忽略?

我写了你引用的答案。 我错了。 我已经更新了它。

我发现NSPersistentContainerperformBackgroundTask没有功能的内部队列,这可能会导致合并冲突。 当我最初测试它时,它似乎确实如此,但我发现像你一样可能存在冲突。 幸运的是,通过创建自己的队列来修复它并不难。 我知道苹果发布如此破碎的东西似乎很奇怪,但情况似乎确实如此。

对于发布不正确的信息,我深表歉意。

来自performBackgroundTask(:)的文档:

每次调用此方法时,持久容器都会创建一个新的 NSManagedObjectContext,并将 concurrencyType 设置为 privateQueueConcurrencyType。然后,持久容器针对上下文的私有队列上新创建的上下文执行传入的块

所以,我不认为这是在做你想做的事情。我认为您想调用newBackgroundContext(),将其存储在某处的属性中,并在需要序列化时使用它performBlock(:)

你的代码中有一个错误,你的DataManager的第77行在后台线程上,并调用rootFolder,然后使用viewContext。您不应该在后台线程上使用 viewContext。在后台线程开始之前,您需要已经拥有 objectID,您可以将 rootFolder.objectID 移到块上方。

相关内容

  • 没有找到相关文章

最新更新