从后台队列调用时,核心数据 NSManagedObjectContext 保存永远不会返回

Posted

技术标签:

【中文标题】从后台队列调用时,核心数据 NSManagedObjectContext 保存永远不会返回【英文标题】:Core Data NSManagedObjectContext save never returns when called from a background queue 【发布时间】:2016-01-08 00:06:41 【问题描述】:

当我在来自单独的后台队列的 NSManagedObjectContext 的私有队列中调用 save() 时,我的应用程序(单元测试)正在停止。它是一个普通的 Core Data 堆栈(具有用于单元测试的内存中持久存储)。

私有队列上下文的重点不就是您不应该关心操作来自哪个队列吗?我应该如何解决这个问题?

我无法单独重现它,但这是我的设置的粗略想法(伪 Swift,跨多个类的压缩调用):

let store = inMemoryStoreCoordinator()

let mainContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
mainContext.persistentStoreCoordinator = store

let childContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
childContext.parentContext = mainContext

let q = NSOperationQueue()
let group = dispatch_group_create()

q.addOperationWithBlock 
    dispatch_group_enter(group)
    childContext.performBlock 
        try! childContext.save()
        dispatch_group_leave(group)
    


dispatch_group_wait(group, DISPATCH_TIME_FOREVER)

【问题讨论】:

添加持久存储协调器会改善情况吗? @pbasdf 不,它没有。我更新了问题,提到我遇到问题的实际代码有一个 PSC。 我试了几次,有时我只得到“a”;有时是“a”和“b”。我认为这可能只是一个时间问题——前台处理在异步之前完成。如果在 dispatch_async 之后添加一些处理,您会同时看到“a”和“b”。 @pbasdf 很有趣,这就解释了为什么操场会表现出这种行为。添加XCPlaygroundPage.currentPage.needsIndefiniteExecution = true 即可。我需要想出一个更好的测试用例。 【参考方案1】:

这可能有几个原因,但我们无法从提供的代码中推断出任何原因。幸运的是,应该很容易调试,只需在死锁(很可能)发生后暂停调试器,看看什么在等待什么。这可能是一个上下文合并僵局,我会把钱放在上面。

【讨论】:

谢谢,但我已经尝试过了,调试器不会暂停。一旦它进入那个状态,我必须停止这个过程。 啊,看到你的更新了。忽略我的回答,留给后人 我在try save() 行设置了一个断点,然后跳过。几秒钟后(此时显然已经死锁,因为没有数据要保存),我点击暂停按钮(或点击 ^⌘Y),但没有任何效果。 我需要更新问题,因为我的操场示例实际上不再演示该问题。我发现它没有等待异步调用完成。 感谢您的帮助,但我想通了。暂停不起作用是非常糟糕的。我猜当主线程出现死锁时会发生这种情况?【参考方案2】:

我意识到是什么导致了僵局。我正在使用调度组来锁定主线程,显然当子上下文保存到其父上下文(主队列上下文)时,这会导致死锁。

【讨论】:

以上是关于从后台队列调用时,核心数据 NSManagedObjectContext 保存永远不会返回的主要内容,如果未能解决你的问题,请参考以下文章

从主队列调用 dispatch_sync 并且执行的块保存到核心数据时 iOS 死锁

多线程核心数据 - NSManagedObject 无效

延迟后在后台线程中处理核心数据

应用从后台恢复时核心数据实体消失

从后台线程保存数据时核心数据崩溃

是否要在后台队列中调用 AVAudioPlayer 的 -prepareToPlay?