NSManagedObjectContext performBlockAndWait:不在后台线程上执行?
我有一个NSManagedObjectContext声明如下所示:
- (NSManagedObjectContext *) backgroundMOC { if (backgroundMOC != nil) { return backgroundMOC; } backgroundMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; return backgroundMOC; }
注意它是用私有队列并发types声明的,所以它的任务应该在后台线程上运行。 我有以下代码:
-(void)testThreading { /* ok */ [self.backgroundMOC performBlock:^{ assert(![NSThread isMainThread]); }]; /* CRASH */ [self.backgroundMOC performBlockAndWait:^{ assert(![NSThread isMainThread]); }]; }
为什么调用performBlockAndWait
执行主线程上的任务而不是后台线程呢?
在另一个答案折腾,尝试解释为什么performBlockAndWait
将始终运行在调用线程。
performBlock
是完全asynchronous的。 它将始终将该块排入接收MOC的队列,然后立即返回。 从而,
[moc performBlock:^{ // Foo }]; [moc performBlock:^{ // Bar }];
将在moc的队列中放置两个块。 它们将总是asynchronous执行。 一些未知的线程会将队列从队列中取出并执行。 另外,这些块被包装在他们自己的自动释放池中,并且它们将代表一个完整的核心数据用户事件( processPendingChanges
)。
performBlockAndWait
不使用内部队列。 这是一个在调用线程的上下文中执行的同步操作。 当然,它会一直等到队列上的当前操作被执行,然后这个块才会在调用线程中执行。 这是logging的(并在几个WWDC演示文稿重申)。
此外, performBockAndWait
是可重入的,所以嵌套的调用都发生在调用线程中。
核心数据工程师非常清楚,基于队列的MOC操作运行的实际线程并不重要。 这是通过使用关键的performBlock*
API进行的同步。
所以,把“performBlock”看作是“这个块被放置在一个队列中,在一些未确定的线程中被执行,一旦它被排入队列,函数就会返回给调用者”
performBlockAndWait
是“这个块将在某个未确定的时间执行,在这个完全相同的线程中执行完毕(在与这个MOC关联的当前队列已经耗尽之后发生)之后,该函数将会返回。
编辑
你确定“performBlockAndWait不使用内部队列”吗? 我认为是的。 唯一的区别是performBlockAndWait将等待块的完成。 而你通过调用线程是什么意思? 在我的理解中,[moc performBlockAndWait]和[moc performBloc]都在其专用队列(背景或主)上运行。 这里的重要概念是moc拥有队列,而不是相反。 如果我错了,请纠正我。 – Philip007
不幸的是,我像我一样措辞的答案,因为它本身就是错误的。 但是,在原始问题的背景下,这是正确的。 具体来说,当在一个专用队列上调用performBlockAndWait
时,该块将在调用该函数的线程上执行 – 它不会被放在队列中并在“专用线程”上执行。
现在,在详细讨论之前,我要强调,依靠图书馆的内部运作是非常危险的。 所有你应该真正关心的是你永远不能期望一个特定的线程来执行一个块,除了任何与主线程有关的东西。 因此,期望performBlockAndWait
不会在主线程上执行,因为它会在调用它的线程上执行。
performBlockAndWait
使用GCD,但它也有自己的层(例如,以防止死锁)。 如果您查看GCD代码(这是开源的),您可以看到同步调用是如何工作的 – 通常它们与队列同步并调用调用该函数的线程上的块 – 除非队列是主队列或一个全局队列。 此外,在WWDC会议上,核心数据工程师强调执行performBlockAndWait
将在调用线程中运行的要点。
所以,当我说它不使用内部队列时,这并不意味着它根本不使用数据结构。 它必须使调用与已经在队列中的块以及在其他线程中提交的块和其他asynchronous调用同步。 但是,在调用performBlockAndWait
它不会将该块放在队列上…而是同步访问并在调用该函数的线程上运行提交的块。
现在,SO不是一个好的论坛,因为它比这更复杂一点,特别是主队列和GCD全局队列 – 但后者对于Core Data来说并不重要。
主要的一点是,当你调用任何performBlock*
或GCD函数时,你不应该期望它在任何特定的线程上运行(除了与主线程绑定的东西),因为队列不是线程,只有主队列将运行一个特定的线程。
在调用核心数据performBlockAndWait
,块将在调用线程中执行(但会与提交给队列的所有内容进行适当的同步)。
我希望这是有道理的,虽然这可能只是造成更多的困惑。
编辑
此外,你可以看到这个不言而喻的含义,因为performBlockAndWait
提供重入支持的方式打破了块的FIFOsorting。 举个例子…
[context performBlockAndWait:^{ NSLog(@"One"); [context performBlock:^{ NSLog(@"Two"); }]; [context performBlockAndWait:^{ NSLog(@"Three"); }]; }];
请注意,严格遵守队列的FIFO保证意味着嵌套的performBlockAndWait
(“Three”)将在asynchronous块(“Two”)之后运行,因为在提交asynchronous块之后提交它。 但是,这不会发生什么,因为这是不可能的…同样的原因嵌套的dispatch_sync
调用发生死锁。 只是要注意如果使用同步版本。
一般情况下,尽可能避免同步版本,因为dispatch_sync
可能导致死锁,任何重入版本,如performBlockAndWait
将不得不作出一些“坏”的决定来支持它…就像有同步版本“跳”队列。
为什么不? Grand Central Dispatch的块并发模式(我假设MOC在内部使用)被devise成只有运行时和操作系统需要担心线程,而不是开发人员(因为操作系统可以做得比你能做得更好,以获得更详细的信息)。 太多人认为队列与线程相同。 他们不是。
排队块不需要在任何给定的线程上运行(主队列中的块必须在主线程上执行)。 所以,实际上,如果运行时觉得它比创build一个线程更有效率,有时同步 (即performBlockAndWait)排队块将运行在主线程上。 由于无论如何你都在等待结果,如果主线程在操作期间挂起,它不会改变你的程序运行的方式。
最后一部分我不确定是否没有记错,但在WWDC 2011关于GCD的video中,我相信有人提到运行时会尽可能在主线程上运行,如果可能的话,进行同步操作,因为它是更高效。 但最后,我认为“为什么”的答案只能由devise系统的人来回答。
我不认为商务部有义务使用后台线程; 如果使用performBlock:
或performBlockAndWait:
它只是确保您的代码不会遇到MOC的并发问题。 由于performBlockAndWait:
应该阻塞当前线程,因此在该线程上运行该块似乎是合理的。
performBlockAndWait:
call只能确保你执行的代码不会引入并发(即在2个线程上performBlockAndWait:
不会同时运行,它们会互相阻塞)。
总之,就是你不能依靠运行MOC操作的线程,基本上也是如此。 我已经学会了如果使用GCD或直接使用线程的困难方式,您必须为每个操作创build本地MOC,然后将其合并到主MOC。
有一个伟大的图书馆( MagicalRecord ),使这个过程非常简单。