保存托管对象上下文会在 iOS 5 的 performBlock 中创建死锁

Posted

技术标签:

【中文标题】保存托管对象上下文会在 iOS 5 的 performBlock 中创建死锁【英文标题】:Saving managed object context creates deadlock in iOS 5's performBlock 【发布时间】:2012-07-13 10:53:43 【问题描述】:

我一直在寻找这个问题的解决方案,但目前还没有找到。

我正在开发一个包含核心数据的 ios 应用。我创建了两个托管对象上下文 (MOC),它们指向同一个持久存储协调器。一个 MOC(称为 self.moc)以主队列并发启动,而另一个 mov(称为 self.bmoc)以私有队列并发启动。我确保 self.moc 只在主线程上运行,而 self.bmoc 只在其 performBlockperformBlockAndWait 块内运行。

但是,我遇到了这种奇怪的情况,我的应用程序在 [self.bmoc save:nil] 行冻结。由于保存操作是在 performBlock 块内执行的,我看不出它有什么原因会陷入死锁。由于它冻结在那条线上,即使我使用[self.bmoc save:&error] 而不是nil,我也不会收到错误。

下面是重现问题的代码。虽然我有许多类似于下面的功能,但只有 这个 会产生问题。我无法弄清楚问题的原因,非常感谢任何见解。谢谢!

-(void)createEmptyUserData 
    [self.bmoc performBlock:^
        User* user = [NSEntityDescription insertNewObjectForEntityForName:@"User" inManagedObjectContext:self.bmoc];
        /* sets user object */
        [self.bmoc save:nil];
    ];

注意:这段代码在主线程中执行。

【问题讨论】:

了解死锁发生在哪里?任何 validateSomething 方法或 willSave 方法锁定某些东西?死锁是2个线程互相等待,另一个线程在哪里等待? 感谢您的回复。执行完这段代码后,主线程继续运行并结束于[self.bmoc performBlockAndWait^],等待上面的代码完成执行,导致主线程冻结。我不知道有任何 validateSomething 方法或 willSave 方法。 您可以尝试添加-com.apple.CoreData.SQLDebug 3 以打开调试,看看是否真的保存了任何内容。 也许这个块实际上正在等待前一个块完成,而这就是卡住的那个? 上下文是嵌套的还是只是共享同一个持久化 t 存储协调器? 【参考方案1】:

在这种情况下,您有两个基本原因会“挂起”。

    您有一个嵌套调用 performBlockAndWait 或其他一些同步线程/队列调用。

    您的一个块没有返回,并且永远运行。

通过查看“挂起”时每个正在运行的线程的堆栈,可以很容易地看到这两者。

performBlock 只需将执行块添加到队列中,然后立即返回。然后其他一些线程从队列中弹出执行块并执行它们。

performBlockAndWait 在调用线程的上下文中执行。基本上,它等待当前排队的执行块运行,然后在当前线程上运行请求的代码。

在调用完成之前它不会返回。

所以,我敢打赌,你要么有多个嵌套调用 performBlockAndWait,要么你的异步执行块之一没有完成。

查看挂起时的堆栈...

或者,记录您的块执行,以便您查看每个块的启动和退出时间。

【讨论】:

这与 fabrice truillot de chambrier 所说的类似。我将根据您提供的方向深入研究问题,稍后再回复您。谢谢。 感谢您指导我回答问题。稍后我将对其进行编辑,并提供一些有关问题原因和解决方法的详细信息。 我以为我可以编辑 cmets,但显然我错了。无论如何,问题的原因是[self.bmoc save:nil]; 在保存过程完成后调用contextDidChange:。在我的contextDidChange: 中,我有[self.moc performBlockAndWait^ //mergeContext ]。正如我在问题下的评论中提到的,我的主线程继续并以[self.bmoc performBlockAndWait^] 结束。问题应该很清楚了:两个队列都在等待对方完成,从而导致死锁。我的解决方案是将contextDidChange: 中的performBlockAndWait 更改为lockunlock 是 contextDidChange: 你的通知处理程序吗?我倾向于调用 [self.moc performBlock^ /* 不管 */ ];过度显式锁定。 是的。由于[self.moc performBlock^ /* whatever */ ] 导致死锁,我认为我必须使用显式锁。

以上是关于保存托管对象上下文会在 iOS 5 的 performBlock 中创建死锁的主要内容,如果未能解决你的问题,请参考以下文章

撤消核心数据托管对象

为啥保存托管对象上下文更改 isDeleted 值?

iOS - 从本地通知启动时托管对象上下文崩溃应用程序

ios/objective-c:保存核心数据时出错

托管对象上下文未保存到持久存储

核心数据获取...为啥在将托管对象插入上下文 A 并保存上下文 A 后,不使用上下文 B 获取托管对象?