我正在后台线程(使用GCD)通过后台线程拥有的新创建的NSManagedObjectContext执行昂贵的获取(约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关闭以进行提取,希望它不需要在{{ 1}}这需要锁定商店。
在开始BT之前,我注意到_processReferenceQueue
集中有一个对象。插入或删除的集合中没有对象。
调用[MOC updatedObjects]
后,[MOC save]
设置为空,如预期的那样
然而,一旦我开始BT,MT仍然试图将商店锁定在[MOC updatedObjects]
内,即使没有在MOC中应该是脏的。
我尝试的下一件事(严格来说是测试)是在开始BT之前调用[_processReferenceQueue
。同样,重置后MOC reset]
设置为空,如预期的那样。在代码的这一点上,我没有触及MT上的任何托管对象,直到BT完成其工作(所以我没有遇到任何问题,因为重置使我已经引用的托管对象无效)。但是,这一次,MT确实 NOT 试图锁定[MOC updatedObjects]
中的持久存储。
这种行为告诉我,在我开始BT之后,我没有对MT上的MOC做任何明确的事情。否则,MT会在_processReferenceQueue
的内部或外部请求锁定(用于读取或写入)。但它没有。
我不确定为什么最近保存的MOC需要在_processReferenceQueue
中进行后续锁定,而最近重置的MOC则不需要。{/ p>
我会继续挖掘。
谢谢! 布赖恩
答案 0 :(得分:2)
如果没有更多信息,我所能做的只是猜测。
不幸的是,Core Data没有使用相同的持久存储协调器为MOC实现读/写锁。这意味着对一个线程的读取将阻止使用相同持久性存储协调器的其他线程。
因此,在后台进行的长时间提取会阻止主线程的获取。
您有几种选择。
分解长时间运行的提取,以便按顺序获取小块。你想做多个小的提取,而不是在当前的一个完成之前发出下一个。
这将允许主线程上的提取有机会在后台线程上的多个小提取之间进行处理。
另一种方法是创建一个单独的持久性存储协调器,将您的后台MOC连接到该协调器,并运行您的提取。如果您使用多个持久性存储协调器,则可以同时拥有多个读取器,并且它们不会永久地相互阻塞。
修改强>
我绝对想过要分手。但事实并非如此 在那里,我试图让两个同时进行的游戏很好玩 一起。此时MT不应该触及核心数据。 - BrianJStafford
您使用的是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.现在,创建一个私有队列MOC,通过performBlock
进行简单的事务处理看看你是否遇到了断点。
你可以看到它的发展方向。基本上,确定非常简单的用例组合会导致您在主线程中击中断点。
至少,这会让你知道框架本身是否/何时导致这种情况发生,这反过来会让你知道你的复杂应用程序正在做什么。
修改强>
好奇心让我变得更好。我刚刚运行了几个简单的测试,在两个设置断点:
-[NSManagedObjectContext(_NSInternalAdditions) lockObjectStore]
-[_PFManagedObjectReferenceQueue _processReferenceQueue:]
除非我真的搞砸了MOC,否则我看不到任何打击。每个MOC都是限制并发类型。我创建了一个包含100,000个简单实体的数据库。
主线程使用FRC加载数据。我添加了一个按钮,当按下该按钮时,将启动dispatch_async
并获取所有100,000个对象。
我看到两个断点都在补充线程中命中,但是在主线程中都没有命中。如果我在主线程上使用MOC获取或更新,当然,我点击了那些断点。
我正在模拟器上运行。
因此,我的结论是,CoreData本身并不对您所看到的行为负责。
可能是由于MOC上的某些配置,持久存储协调器,持久存储或您的上下文之间的某些未知交互。
如果没有关于对象配置和实际代码的进一步信息,我最好的结论是你在代码中做了“某事”以使其发生。
您应该设置这些断点,并查看代码被击中时发生的情况。