【问题标题】:Main thread waiting on [NSManagedObjectContext(_NSInternalAdditions) lockObjectStore] while background thread is executing主线程在后台线程执行时等待 [NSManagedObjectContext(_NSInternalAdditions) lockObjectStore]
【发布时间】:2012-08-28 23:03:41
【问题描述】:

我正在后台线程(使用 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: 的调用,这导致尝试锁定存储。有谁碰巧知道这个方法的作用以及我如何/如果我可以在我的后台线程正在执行它的工作时阻止它被调用?

编辑

在我开始后台获取之后,我没有在主线程上进行任何核心数据读取或写入。这就是为什么这很令人费解。如果主线程也试图做更多的工作,我希望会有争执,但事实并非如此——至少,我没有要求它这样做。没有读取,没有写入,没有 FRC。这就是为什么我想知道是否有人熟悉这个 _processReferenceQueue 方法。为什么会被调用?我能做什么导致它运行?

编辑

作为测试,我试图让 MT 的 MOC 进入一个状态,在我关闭 BT 进行提取之前,它没有待处理的更改,希望它不需要在 @987654322 中做任何工作@ 这将需要对商店进行锁定。

在开始 BT 之前,我注意到 [MOC updatedObjects] 集合中有一个对象。插入或删除的集合中没有对象。

调用[MOC save] 后,[MOC updatedObjects] 集合为空,正如预期的那样。
但是,一旦我启动了 BT,MT 仍然试图将商店锁定在 _processReferenceQueue 内,即使在 MOC 中没有什么应该是脏的。

我尝试的下一件事(严格作为测试)是在启动 BT 之前调用 [MOC reset]。同样,[MOC updatedObjects] 集在重置后为空,正如预期的那样。在代码中的这一点上,在 BT 完成其工作之前,我不会接触 MT 上的任何托管对象(因此,由于重置使我已经引用的托管对象无效,我不会遇到任何问题)。然而这一次,MTNOT尝试将持久性存储锁定在_processReferenceQueue

这种行为向我表明,在我启动 BT 后,我没有对 MT 上的 MOC 做任何明确的事情。否则,MT 将在_processReferenceQueue 内部或外部的某个时间点(用于读取或写入)请求锁定。但它没有。

我不确定为什么最近保存的 MOC 需要随后锁定 _processReferenceQueue 而最近重置的 MOC 不需要。

我会继续挖掘。

谢谢! 布赖恩

【问题讨论】:

  • 我有一个想法,但是今天因为回答一个信息不足的问题而被烧了两次。请发布更多信息,具体说明您在后台线程中正在做什么,以及您在主线程中正在做什么(即,您是否在后台进行任何写入,您是否在主线程上有 FRC)。 ..等
  • 另外,请记住,您不能跨线程传递获取的对象。在后台使用这 30k 个对象,或传递它们的 ID。
  • 以上更新。此外,即使我只是在后台进行提取并忽略结果(作为测试),MT 上的锁定也会发生。我确实将 objectID 传递回 MT。
  • 最后一部分...你重置 MOC 的地方。在那之前它的状态是什么?尽可能转储所有属性。
  • 很好的建议。我查看了保存前后以及重置前后的 MOC。在这两种情况下,之前的状态似乎是相同的。我在 after 状态中看到的唯一区别是重置后 _cd_rc ivar 仍为 0,而在保存后它变为 2。我不确定那个变量是什么,但我想知道保存后它的非零值是否是 _processReferenceQueue 需要对存储进行锁定的原因。

标签: iphone ios multithreading core-data


【解决方案1】:

没有更多信息,我只能猜测。

不幸的是,Core Data 没有使用相同的持久存储协调器为 MOC 实现读/写锁。这意味着对一个线程的读取将阻塞使用相同持久存储协调器的其他线程。

因此,您在后台长时间运行的抓取将阻止主线程抓取。

您有多种选择。

分解长时间运行的提取,以便它按顺序提取小块。您想进行多次小提取,而不是在当前提取完成之前发布下一个。

这将允许主线程上的提取有机会在后台线程上的多个小提取之间进行处理。

另一种选择是创建一个单独的持久存储协调器,将您的后台 MOC 连接到该协调器,然后运行您的 fetch。如果您使用多个持久存储协调器,您可以同时拥有多个读取器,并且它们不会永久相互阻塞。

编辑

我确实考虑过分手。但这不是个案 我试图让两个同时提取的效果很好 一起。 MT 在这一点上不应该接触核心数据。 – 布赖恩斯塔福德

您是在使用UIManagedDocument,还是在主线程上创建了MOC,或者使用NSMainQueueConcurrencyType

我问是因为 Core Data 确保每次都通过运行循环处理所有“用户事件”。我从来没有深入研究过这是如何完成的,但我想他们会在主线程上的 MOC 的运行循环中添加一个处理程序。

Core Data 使用起来非常简单,除非您有多个 MOC。然后,交互非常复杂,如果没有实际代码,很难确定发生了什么。

如果可以,请发布创建任何/所有 MOC 的代码,以及使用这些 MOC 的代码。

如果与当前上下文无关,我真诚地怀疑他们是否会让 run-loop-processing 实际上获得对存储的锁定,因此可能与后台 MOC 存在一些隐藏的交互。我敢打赌,您的应用程序中仍然存在您认为自己没有在做的事情,这会以某种方式与主 MOC 进行后台工作。

如果需要,您可以轻松追踪[_PFManagedObjectReferenceQueue _processReferenceQueue:] 的调用方式。

测试1.创建一个简单的项目,勾选Core Data框,得到一个简单的Core Data模板项目。在_processReferenceQueue: 处设置一个断点只是为了确保您可以获取该断点。

测试 2。 注释掉实例化核心数据内容的部分,并查看是否仅通过链接到框架即可到达断点。

测试 3。 创建 MOC,但不要对其进行任何其他操作。只需创建它。重复以查看是否命中断点。

Test 4. 去掉“默认 MOC”,这样你基本上就和 Test 2 一样了。现在,创建一个私有队列 MOC,通过performBlock 与它进行简单的交易,然后查看如果你遇到断点。

你可以看到这是怎么回事。基本上,确定哪些非常简单的用例组合会导致您在主线程中遇到断点。

这至少会让您知道框架本身是否/何时导致这种情况发生,进而让您了解您的复杂应用正在做什么。

编辑

好奇心战胜了我。我只是进行了一些简单的测试,在两者都设置了断点:

-[NSManagedObjectContext(_NSInternalAdditions) lockObjectStore]
-[_PFManagedObjectReferenceQueue _processReferenceQueue:]

除非我真的在搞乱 MOC,否则我看不到任何命中。每个 MOC 都是约束并发类型。我创建了一个包含 100,000 个简单实体的数据库。

主线程使用 FRC 加载数据。我添加了一个按钮,按下该按钮会启动 dispatch_async 并获取所有 100,000 个对象。

我看到两个断点都在补充线程中命中,但在主线程中都没有命中。如果我在主线程上使用 MOC 获取或更新,当然,我会遇到这些断点。

我正在模拟器上运行。

因此,我的结论是 CoreData 本身不对您所看到的行为负责。

可能是由于 MOC 上的某些配置、持久存储协调器、持久存储或您的上下文之间的某些未知交互。

如果没有关于您的对象配置和实际代码的更多信息,我最好的结论是您在代码中做了“某些事情”来导致这种情况发生。

您应该设置这些断点,并查看当它们被击中时代码中发生了什么。

【讨论】:

  • 我确实考虑过分手。但这不是我试图让两个同时获取很好地一起玩的情况。在这一点上,MT 不应该接触 Core Data。
  • 你在使用 UIManagedDocument 吗?您是否在主线程或使用 NSMainQueueConcurrencyType 创建了 MOC?
  • 我没有使用 UIManagedDocument。 MT 上有一个 MOC,默认并发类型(NSConfinementConcurrencyType)。后台线程有自己的 MOC,也有默认的并发类型。
  • 主线程上的 MOC 应该在每次运行循环迭代结束时触发检查至少 'processPendingChanges' 但就像我在编辑中说的那样,我不认为他们会锁定商店除非上下文本身发生了变化……而你说你没有改变上下文……
  • 感谢您的建议。我会沿着这些思路做更多的探索。
猜你喜欢
  • 2012-08-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-14
  • 1970-01-01
相关资源
最近更新 更多