NSManagedObjectContext performBlockAndWait:不在后台线程上执行?

时间:2012-08-06 16:07:09

标签: iphone objective-c ios core-data

我有一个NSManagedObjectContext声明如下:

- (NSManagedObjectContext *) backgroundMOC {
    if (backgroundMOC != nil) {
        return backgroundMOC;
    }
    backgroundMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    return backgroundMOC;
}

请注意,它是使用私有队列并发类型声明的,因此它的任务应该在后台线程上运行。我有以下代码:

-(void)testThreading
{
    /* ok */
    [self.backgroundMOC performBlock:^{
        assert(![NSThread isMainThread]); 
    }];

    /* CRASH */
    [self.backgroundMOC performBlockAndWait:^{
        assert(![NSThread isMainThread]); 
    }];
}

为什么调用performBlockAndWait在主线程而不是后台线程上执行任务?

4 个答案:

答案 0 :(得分:95)

抛弃另一个答案,尝试解释为什么performBlockAndWait将始终在调用线程中运行。

performBlock完全异步。它总是将块排入接收MOC的队列,然后立即返回。因此,

[moc performBlock:^{
    // Foo
}];
[moc performBlock:^{
    // Bar
}];

将在队列中放置两个块用于moc。它们总是异步执行。一些未知的线程会从队列中拉出块并执行它们。此外,这些块包含在它们自己的自动释放池中,并且它们也代表完整的Core Data用户事件(processPendingChanges)。

performBlockAndWait不使用内部队列。它是在调用线程的上下文中执行的同步操作。当然,它将一直等到队列上的当前操作被执行,然后该块将在调用线程中执行。这是记录在案的(并在几个WWDC演示文稿中重申)。

此外,performBockAndWait是可重入的,因此嵌套调用都发生在该调用线程中。

核心数据工程师已经非常清楚,基于队列的MOC操作运行的实际线程并不重要。它是使用performBlock* API的同步进行同步的。

所以,考虑' performBlock' as"此块被放置在队列中,在某个未确定的时间执行,在某个未确定的线程中执行。该功能将在排队后立即返回给呼叫者"

performBlockAndWait是"此块将在某个未确定的时间执行,在这个完全相同的线程中。该代码完全执行后将返回该函数(这将在与此MOC关联的当前队列耗尽后发生)。"

修改

  

你确定" performBlockAndWait不使用内部队列"?   我认为确实如此。唯一的区别是performBlockAndWait会   等到阻止完成。你打电话是什么意思   线?据我所知,[moc performBlockAndWait]和[moc   performBloc]都在其私有队列(后台或主队列)上运行。该   这里重要的概念是moc拥有队列,而不是其他方式   周围。如果我错了,请纠正我。 - Philip007

不幸的是,我像我一样表达了答案,因为它本身就是不正确的。但是,在原始问题的背景下,这是正确的。具体来说,当在私有队列上调用performBlockAndWait时,该块将在调用该函数的线程上执行 - 它不会被放入队列并在&#34;私有线程上执行。&#34; < / p>

现在,在我进入细节之前,我想强调一下,取决于图书馆的内部运作是非常危险的。所有你真正关心的是你永远不会指望一个特定的线程来执行一个块,除了与主线程相关的任何东西。因此,不建议在主线程上执行performBlockAndWait而不是,因为它将在调用它的线程上执行。

performBlockAndWait使用GCD,但它也有自己的层(例如,为了防止死锁)。如果你看一下GCD代码(它是开源的),你可以看到同步调用是如何工作的 - 通常它们与队列同步并在调用函数的线程上调用块 - 除非队列是主队列或者全局队列。此外,在WWDC会谈中,核心数据工程师强调performBlockAndWait将在调用线程中运行。

因此,当我说它不使用内部队列时,这并不意味着它根本不使用数据结构。它必须将调用与队列中已有的块以及在其他线程和其他异步调用中提交的块同步。但是,当调用performBlockAndWait时,它不会将块放在队列上......而是同步访问并在调用该函数的线程上运行提交的块。

现在,SO不是一个很好的论坛,因为它比这更复杂,尤其是主队列和GCD全局队列 - 但后者对核心数据并不重要。

重点是,当你调用任何performBlock*或GCD函数时,你不应该期望它在任何特定线程上运行(除了绑定到主线程的东西),因为队列不是线程,只有主队列将在特定线程上运行块。

当调用核心数据performBlockAndWait时,该块将在调用线程中执行(但将与提交给队列的所有内容进行适当的同步)。

我希望这是有道理的,尽管它可能只会引起更多混乱。

修改

此外,您可以看到这一点的未说明的含义,因为performBlockAndWait提供可重入支持的方式会破坏块的FIFO排序。举个例子......

[context performBlockAndWait:^{
    NSLog(@"One");
    [context performBlock:^{
        NSLog(@"Two");
    }];
    [context performBlockAndWait:^{
        NSLog(@"Three");
    }];
}];

请注意,严格遵守队列的FIFO保证意味着嵌套的performBlockAndWait(&#34; Three&#34;)将在异步块之后运行(&#34; Two&#34;)因为它是在提交异步块后提交的。然而,这不是发生的事情,因为它是不可能的......出于同样的原因,嵌套dispatch_sync调用会导致死锁。如果使用同步版本,请注意一些事项。

一般情况下,尽可能避免使用同步版本,因为dispatch_sync会导致死锁,任何可重入版本(例如performBlockAndWait)都必须使某些“错误”#34;决定支持它...比如同步版本&#34;跳跃&#34;队列。

答案 1 :(得分:3)

为什么不呢? Grand Central Dispatch的块并发范例(我假设MOC在内部使用)的设计使得只有运行时和操作系统需要担心线程,而不是开发人员(因为操作系统可以做得比获得更详细的信息更好) )。太多人认为队列与线程相同。他们不是。

不需要在任何给定线程上运行排队块(例外情况是主队列中的块必须在主线程上执行)。所以,事实上,如果运行时觉得它比为它创建一个线程更有效,有时 sync (即performBlockAndWait)排队的块将在主线程上运行。由于您无论如何都在等待结果,如果主线程在操作期间挂起,它将不会改变程序的运行方式。

这最后一部分我不确定我是否记得正确,但在WWDC 2011关于GCD的视频中,我认为有人提到运行时会努力在主线程上运行,如果可能的话,用于同步操作因为它更有效率。最后,我认为“为什么”的答案只能由设计系统的人来回答。

答案 2 :(得分:0)

我认为MOC没有义务使用后台线程;如果您使用performBlock:performBlockAndWait:,则必须确保您的代码不会遇到与MOC的并发问题。由于performBlockAndWait:应该阻止当前线程,因此在该线程上运行该块似乎是合理的。

答案 3 :(得分:0)

performBlockAndWait:调用只确保以不引入并发的方式执行代码(即2个线程performBlockAndWait:不会同时运行,它们会阻塞彼此)。

它的长期和短期是你不能依赖MOC操作运行的线程,基本上就是这样。我已经学到了很难,如果你使用GCD或直接线程,你总是必须为每个操作创建本地MOC,然后将它们合并到主MOC。

有一个很棒的库(MagicalRecord)使这个过程非常简单。