Core Data 私有队列死锁

Posted

技术标签:

【中文标题】Core Data 私有队列死锁【英文标题】:Core Data private queue deadlock 【发布时间】:2012-09-14 18:08:26 【问题描述】:

我有一个后台线程做一些处理,并保存到核心数据。在我的应用程序委托的applicationShouldTerminate 中,我等待后台线程完成其工作时释放的信号量。这是为了避免在工作过程中杀死线程并使事情处于不一致的状态。

不幸的是,这会导致死锁。以下是后台作业的运行方式:

_context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_context setParentContext:_parentContext];

[_context performBlock:
^
    // ... long-running task here ...

    NSError * error;
    [_context save:&error]; // deadlock here if main thread is waiting on semaphore

    // ... release semaphore here ...
];

如果用户在后台线程仍在工作时退出应用程序,则会死锁。问题似乎是[_context save:&error] 正在将dispatch_sync(或等效项)调用到主线程中-但主线程已经在等待该线程释放信号量,因此无法运行该块。

由于子上下文保存似乎在主线程上阻塞,如何实现这种模式(主线程等待子完成并保存其上下文)?

主线程:

#0  0x00007fff882e96c2 in semaphore_wait_trap ()
#1  0x00007fff876264c2 in _dispatch_semaphore_wait_slow ()
#2  0x00000001001157fb in +[IndxCore waitForBackgroundJobs] at /Users/mspong/dev/Indx/IndxCore/IndxCore/IndxCore.m:48
#3  0x00000001000040c6 in -[RHAppDelegate applicationShouldTerminate:] at /Users/mspong/dev/Indx/Indx/Indx/RHAppDelegate.m:324
#4  0x00007fff9071a48f in -[NSApplication _docController:shouldTerminate:] ()
#5  0x00007fff9071a39e in __91-[NSDocumentController(NSInternal) _closeAllDocumentsWithDelegate:shouldTerminateSelector:]_block_invoke_0 ()
#6  0x00007fff9071a23a in -[NSDocumentController(NSInternal) _closeAllDocumentsWithDelegate:shouldTerminateSelector:] ()
(snip)
#17 0x00007fff9048e656 in NSApplicationMain ()
#18 0x0000000100001e72 in main at /Users/mspong/dev/Indx/Indx/Indx/main.m:13
#19 0x00007fff8c4577e1 in start ()

后台线程:

#0  0x00007fff882e96c2 in semaphore_wait_trap ()
#1  0x00007fff87627c6e in _dispatch_thread_semaphore_wait ()
#2  0x00007fff87627ace in _dispatch_barrier_sync_f_slow ()
#3  0x00007fff8704a78c in _perform ()
#4  0x00007fff8704a5d2 in -[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:] ()
#5  0x00007fff8702c278 in -[NSManagedObjectContext save:] ()
#6  0x000000010011640d in __22-[Indexer updateIndex]_block_invoke_0 at /Users/mspong/dev/Indx/IndxCore/IndxCore/Indexer/Indexer.m:70
#7  0x00007fff87079b4f in developerSubmittedBlockToNSManagedObjectContextPerform_privateasync ()
#8  0x00007fff876230fa in _dispatch_client_callout ()
#9  0x00007fff876244c3 in _dispatch_queue_drain ()
#10 0x00007fff87624335 in _dispatch_queue_invoke ()
#11 0x00007fff87624207 in _dispatch_worker_thread2 ()
#12 0x00007fff88730ceb in _pthread_wqthread ()
#13 0x00007fff8871b1b1 in start_wqthread ()

【问题讨论】:

这让我想起了这个问题:***.com/questions/11786436/… 作为一种解决方法,您可以使用dispatch_semaphore_wait,并在循环中超时 1 秒,直到成功。 我一直在考虑这样的变通办法 - 但我需要立即从applicationShouldTerminate 返回以完成任何这项工作。您是否建议返回NSTerminateCancel,然后在我获得信号量后启动另一个退出?对于苹果必须考虑过的基本问题,这似乎是一个愚蠢的解决方法。 我没有这方面的实践经验,但是根据applicationShouldTerminate:的文档,返回NSTerminateCancel是完全可以的。保存完成后,您的后台线程可以调用replyToApplicationShouldTerminate:(可能包装到performSelectorOnMainThread)。那么你根本不需要信号量。 啊哈!我不知道NSTerminateLaterreplyToApplicationShouldTerminate。这似乎很好地解决了我的问题。我仍然需要信号量(可能有多个后台作业正在运行),但这给了我一个可以使用的框架。您能否将您的回复发布为答案,以便我将其标记为已接受? 【参考方案1】:

applicationShouldTerminate: 中,您可以返回NSTerminateLater 以延迟终止,直到后台上下文完成保存操作。

保存完成后调用

[[NSApplication sharedApplication] replyToApplicationShouldTerminate:YES];

退出应用程序。

【讨论】:

更正 - applicationShouldTerminate: 需要返回 NSTerminateLater 才能完成这项工作。但这就是答案。谢谢! @Xtapolapocetl:你说得对,我会在答案中更正。

以上是关于Core Data 私有队列死锁的主要内容,如果未能解决你的问题,请参考以下文章

将主上下文和私有上下文与 Core Data 合并

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

私有队列 NSManagedObjectContext 在无限循环中保存结束

关于访问MSMQ远端私有队列的一点经验

获取 .NET Core JsonSerializer 以序列化私有成员

无权访问私有 MSMQ