我正在通过后台线程拥有的新创建的NSManagedObjectContext在后台线程(使用GCD)上执行昂贵的读取操作(约5秒,约30,000个对象)。
但是,我没有在后台执行此操作,因为主线程正在等待持久性存储上的锁定,因此UI被冻结。这是堆栈跟踪:
* thread #1: tid = 0x1c03, 0x3641be78 CoreData`-[NSManagedObjectContext(_NSInternalAdditions) lockObjectStore], stop reason = breakpoint 1.1
frame #0: 0x3641be78 CoreData`-[NSManagedObjectContext(_NSInternalAdditions) lockObjectStore]
frame #1: 0x36432f06 CoreData`-[_PFManagedObjectReferenceQueue _processReferenceQueue:] + 1754
frame #2: 0x36435fd6 CoreData`_performRunLoopAction + 202
frame #3: 0x35ab9b1a CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 18
frame #4: 0x35ab7d56 CoreFoundation`__CFRunLoopDoObservers + 258
frame #5: 0x35ab80b0 CoreFoundation`__CFRunLoopRun + 760
frame #6: 0x35a3b4a4 CoreFoundation`CFRunLoopRunSpecific + 300
frame #7: 0x35a3b36c CoreFoundation`CFRunLoopRunInMode + 104
frame #8: 0x376d7438 GraphicsServices`GSEventRunModal + 136
frame #9: 0x33547cd4 UIKit`UIApplicationMain + 1080
frame #10: 0x000f337a MyApp`main + 90 at main.m:16
我(相信)我已经确认在此后台线程正在执行其工作时,我没有访问主线程的NSManagedObjectContext。从堆栈跟踪中可以明显看出,我没有直接对Core Data做任何事情。但是某事触发了对_processReferenceQueue:的调用,这导致尝试锁定存储。有人碰巧知道此方法的作用以及在后台线程执行其工作时如何/是否可以防止它被调用?
编辑
在启动此后台获取之后,我不会在主线程上进行任何Core Data读取或写入操作。这就是为什么如此令人困惑。如果主线程也试图做更多的工作,我希望会引起争执,但这不是-至少,我没有要求这样做。没有读取,没有写入,没有FRC。这就是为什么我想知道是否有人熟悉此_processReferenceQueue方法。为什么叫它?我该怎么办才能导致它运行?
编辑
作为测试,我尝试将MT的MOC设置为没有待定更改的状态,然后再关闭BT进行提取,希望它不需要在
_processReferenceQueue
中进行任何需要锁定商店。在启动BT之前,我注意到
[MOC updatedObjects]
集中只有一个对象。插入或删除的集中没有对象。调用
[MOC save]
之后,[MOC updatedObjects]
集为空,如预期的那样。但是,一旦我启动了BT,即使MOC中没有脏东西,MT仍会尝试将商店锁定在
_processReferenceQueue
中。我尝试做的下一件事(严格地作为测试)是在启动BT之前调用[
MOC reset]
。同样,重置后的[MOC updatedObjects]
集为空,这与预期的一样。此时,在代码中,直到BT完成工作,我才接触MT上的任何托管对象(因此,由于重置使我已经引用的托管对象无效,因此我没有遇到任何问题)。但是,这次MT做了而不是尝试将持久性存储锁定在_processReferenceQueue
中。这种行为向我表明,在启动BT之后,我没有对MT上的MOC进行任何明确的操作。否则,MT会在
_processReferenceQueue
内或外的某个位置请求锁定(用于读取或写入)。但事实并非如此。我不确定为什么最近保存的MOC要求随后锁定
_processReferenceQueue
,而最近重置的MOC不需要。我会继续挖掘。
谢谢!
布赖恩
最佳答案
没有更多信息,我所能做的只是猜测。
不幸的是,Core Data没有使用相同的持久性存储协调器为MOC实现读取器/写入器锁定。这意味着对一个线程的读取将阻止其他使用同一持久性存储协调器的线程。
因此,您在后台长时间运行的访存将阻止主线程访存。
您有几种选择。
分解长时间运行的获取,以便按顺序获取小片段。您想进行多次小量获取,而不要在完成当前任务之前再发行下一部分。
这将允许在主线程上进行访存,从而有机会在后台线程上的多个小访存之间进行处理。
另一种选择是创建一个单独的持久性存储协调器,将您的后台MOC连接到该持久性存储协调器,然后运行获取。如果您使用多个持久性存储协调器,那么您可以同时拥有多个阅读器,并且它们不会永久性地相互阻塞。
编辑
您是在使用UIManagedDocument
还是在主线程上创建了MOC,还是使用NSMainQueueConcurrencyType
?
我问,因为Core Data确保每次通过运行循环都处理所有“用户事件”。我从没想过要确切地确定该如何完成,但是我想他们会在主线程上的MOC的运行循环中添加一个处理程序。
核心数据非常易于使用,除非您有多个MOC。然后,交互是如此复杂,以至于没有实际代码就很难确定正在发生的事情。
如果可以,请发布创建任何/所有MOC的代码以及使用这些MOC的代码。
我真诚地怀疑,如果与当前上下文无关,那么运行循环处理实际上是否会在商店上获得一个锁,因此,可能与后台MOC发生了一些隐藏的交互。我敢打赌,您的应用程序中仍然存在某些东西,您认为自己没有这样做,这在某种程度上使后台工作与主MOC交叉了。
如果需要,可以轻松跟踪[_PFManagedObjectReferenceQueue _processReferenceQueue:]
的调用方式。
测试1. 创建一个简单项目,选中Core Data框以获取一个简单的Core Data模板项目。在_processReferenceQueue:
处放置一个断点只是为了确保您可以获取该断点。
测试2。注释掉实例化核心数据的部分,并查看是否仅通过链接到框架即可达到断点。
测试3. 创建MOC,但不要对其进行任何其他操作。只需创建它。重复查看是否达到断点。
测试4。摆脱“默认MOC”,基本上就像测试2。现在,创建一个私有(private)队列MOC,并通过performBlock
与它进行简单的交易,看看是否达到断点。
您可以看到前进的方向。基本上,确定非常简单的用例的哪种组合会导致您在主线程中达到断点。
至少,这将让您知道框架本身是否/何时导致这种情况发生,这反过来又应该使您了解您的复杂应用程序在做什么。
编辑
好奇心使我变得更好。我只是运行了一些简单的测试,在这两个位置都设置了断点:-[NSManagedObjectContext(_NSInternalAdditions) lockObjectStore]
-[_PFManagedObjectReferenceQueue _processReferenceQueue:]
除非我实际上与MOC发生冲突,否则我看不到任何打击。每个MOC都是限制并发类型。我创建了一个具有100,000个简单实体的数据库。
主线程使用FRC加载数据。我添加了一个按钮,按下该按钮将启动dispatch_async
并获取所有100,000个对象。
我看到两个断点都在辅助线程中命中,但是都没有在主线程中命中。当然,如果我在主线程上获取或更新了MOC,我会遇到这些断点。
我正在模拟器上运行。
因此,我的结论是,CoreData本身不应对您看到的行为负责。
可能是由于MOC上的某些配置,持久性存储协调器,持久性存储或上下文之间的某些未知交互。
没有关于对象和实际代码的配置的进一步信息,我的最佳结论是,您正在代码中执行“某些操作”以使这种情况发生。
您应该设置这些断点,并查看当它们被击中时代码中正在发生的事情。
关于iphone - 主线程在执行后台线程时正在等待[NSManagedObjectContext(_NSInternalAdditions)lockObjectStore],我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/12168761/