折腾另一个答案,试图解释为什么performBlockAndWait
总是在调用线程中运行。
performBlock
是完全异步的。它总是将块排入接收 MOC 的队列,然后立即返回。因此,
[moc performBlock:^{
// Foo
}];
[moc performBlock:^{
// Bar
}];
将在 moc 队列中放置两个块。它们将始终异步执行。一些未知线程将从队列中拉出块并执行它们。此外,这些块被包装在它们自己的自动释放池中,它们也将代表一个完整的 Core Data 用户事件 ( processPendingChanges
)。
performBlockAndWait
不使用内部队列。它是在调用线程的上下文中执行的同步操作。当然,它会等到队列上的当前操作执行完毕,然后该块将在调用线程中执行。这已记录在案(并在多个 WWDC 演示文稿中重申)。
此外,performBockAndWait
它是可重入的,因此嵌套调用都发生在该调用线程中。
Core Data 工程师已经非常清楚,基于队列的 MOC 操作运行的实际线程并不重要。关键是使用performBlock*
API 进行同步。
因此,将'performBlock'视为“此块被放置在队列中,将在某个未确定的时间,在某个未确定的线程中执行。该函数将在入队后立即返回给调用者”
performBlockAndWait
是“这个块将在某个不确定的时间,在这个完全相同的线程中执行。该函数将在此代码完全执行后返回(这将在与此 MOC 关联的当前队列耗尽后发生)。”
编辑
您确定“performBlockAndWait 不使用内部队列”吗?我认为确实如此。唯一的区别是 performBlockAndWait 将等到块完成。调用线程是什么意思?据我了解,[moc performBlockAndWait] 和 [moc performBloc] 都在其私有队列(后台或主)上运行。这里的重要概念是 moc 拥有队列,而不是相反。如果我错了,请纠正我。– 菲利普007
不幸的是,我按照自己的方式表达了答案,因为就其本身而言,它是不正确的。但是,在原始问题的上下文中,它是正确的。具体来说,当调用performBlockAndWait
私有队列时,该块将在调用该函数的线程上执行——它不会被放入队列并在“私有线程”上执行。
现在,在我深入细节之前,我想强调一下,依赖库的内部工作是非常危险的。您真正应该关心的是,您永远不能期望特定线程执行块,除非与主线程相关联。因此,不建议期望 a不在performBlockAndWait
主线程上执行,因为它将在调用它的线程上执行。
performBlockAndWait
使用 GCD,但它也有自己的层(例如,防止死锁)。如果您查看 GCD 代码(它是开源的),您可以看到同步调用是如何工作的 - 通常它们与队列同步并调用调用函数的线程上的块 - 除非队列是主队列或一个全局队列。此外,在 WWDC 会谈中,Core Data 工程师强调performBlockAndWait
将在调用线程中运行的点。
所以,当我说它不使用内部队列时,并不意味着它根本不使用数据结构。它必须将调用与队列中已经存在的块以及在其他线程和其他异步调用中提交的块同步。但是,当调用performBlockAndWait
它时,它不会将块放在队列中......相反,它会同步访问并在调用该函数的线程上运行提交的块。
现在,SO 不是一个很好的论坛,因为它比这更复杂一些,尤其是主队列和 GCD 全局队列——但后者对 Core Data 并不重要。
要点是,当您调用任何performBlock*
或 GCD 函数时,您不应期望它在任何特定线程上运行(与主线程相关的东西除外),因为队列不是线程,只有主队列会在特定线程上运行块线。
调用核心数据时performBlockAndWait
,该块将在调用线程中执行(但将与提交到队列的所有内容适当同步)。
我希望这是有道理的,尽管它可能只会引起更多的混乱。
编辑
此外,您可以看到这种不言而喻的影响,因为performBlockAndWait
提供可重入支持的方式破坏了块的 FIFO 排序。举个例子...
[context performBlockAndWait:^{
NSLog(@"One");
[context performBlock:^{
NSLog(@"Two");
}];
[context performBlockAndWait:^{
NSLog(@"Three");
}];
}];
请注意,严格遵守队列的 FIFO 保证意味着嵌套performBlockAndWait
(“三”)将在异步块(“二”)之后运行,因为它是在提交异步块之后提交的。dispatch_sync
然而,这不是发生的事情,因为这是不可能的......出于同样的原因,嵌套调用会导致死锁。如果使用同步版本,请注意一些事项。
通常,尽可能避免使用同步版本,因为这dispatch_sync
可能会导致死锁,并且任何可重入版本performBlockAndWait
都必须做出一些“糟糕”的决定来支持它......就像让同步版本“跳入”队列一样。