2

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

编辑

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

编辑

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

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

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

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

这种行为向我表明,在我启动 BT 之后,我没有对 MT 上的 MOC 做任何明确的事情。否则,MT 会在_processReferenceQueue. 但它没有。

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

我会继续挖掘。

谢谢!布赖恩

4

1 回答 1

2

没有更多信息,我所能做的就是猜测。

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

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

你有几个选择。

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

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

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

编辑

我确实想过分手。但这不是我试图让两个同时获取很好地一起玩的情况。MT 在这一点上不应该接触核心数据。– 布赖恩斯塔福德

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

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

Core Data 使用起来非常简单,除非你有多个 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 加载数据。我添加了一个按钮,当按下该按钮时,将启动 adispatch_async并获取所有 100,000 个对象。

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

我在模拟器上运行。

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

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

如果没有有关对象配置和实际代码的更多信息,我最好的结论是您正在代码中执行“某些操作”以导致这种情况发生。

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

于 2012-08-29T02:50:17.553 回答