我有一个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在主线程而不是后台线程上执行任务?

推荐答案

抛出另一个答案,try 解释为什么performBlockAndWait总是在调用线程中运行.

performBlock是完全异步的.它将始终将块排队到接收MOC的队列中,然后立即返回.因此

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

将在主运行中心的队列中放置两个街区.它们将始终异步执行.一些未知线程将从队列中拉出块并执行它们.此外,这些块被包装在它们自己的自动释放池中,它们还将代表一个完整的核心数据用户事件(processPendingChanges).

performBlockAndWait不使用内部队列.它是在调用线程的上下文中执行的同步操作.当然,它将等待队列上的当前操作执行完毕,然后该块将在调用线程中执行.这一点已被记录(并在几次WWDC演示中得到重申).

此外,performBockAndWait是可重入的,所以嵌套调用都会在调用线程中正确发生.

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

因此,考虑"PrimFultBug"为"这个块被放置在队列中,在某个未确定的时间内执行,在某个未确定的线程中.

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

EDIT

您确定"performBlockAndWait不使用内部队列"吗?

不幸的是,我用了同样的措辞来回答这个问题,因为就其本身而言,它是不正确的.然而,在原问题的背景下,它是正确的.具体来说,当在私有队列上调用performBlockAndWait时,块将在调用函数的线程上执行——它不会被放入队列,而是在"私有线程"上执行

现在,在我深入讨论细节之前,我想强调,依赖库的内部工作是非常危险的.您真正应该关心的是,除了与主线程相关的任何内容外,您永远不能期望特定线程执行块.因此,不建议在主线程上执行performBlockAndWaitnot次,因为它将在调用它的线程上执行.

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

所以,当我说它不使用内部队列时,这并不意味着它根本不使用数据 struct .它必须将调用与队列中已经存在的块以及在其他线程和其他异步调用中提交的块同步.然而,当拨打performBlockAndWait时,它不会将阻塞置于队列中...相反,它会同步访问,并在调用函数的线程上运行提交的块.

现在,SO并不是一个很好的论坛,因为它比它复杂一点,特别是w.r.t主队列和GCD全局队列——但后者对于核心数据并不重要.

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

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

我希望这是有道理的,尽管它可能只是造成了更多的混乱.

EDIT

此外,您可以看到这一点的潜在影响,即performBlockAndWait提供可重入支持的方式打破了块的FIFO顺序.举个例子...

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

请注意,严格遵守队列的FIFO保证将意味着嵌套performBlockAndWait("三")将在异步块("两")之后运行,因为它是在异步块提交之后提交的.然而,事实并非如此,因为这是不可能的...出于同样的原因,嵌套的dispatch_sync个调用会导致死锁.如果使用同步版本,需要注意的是.

一般来说,尽可能避免同步版本,因为dispatch_sync会导致死锁,任何可重入版本,比如performBlockAndWait,都必须做出一些"糟糕"的决定来支持它...比如让同步版本"跳转"队列.

Objective-c相关问答推荐

如果DISPATCH_SOURCE_SET_EVENT_HANDLER中的SELF需要为弱

UITableViewCell:如何防止蓝色 Select ​​背景不带 isSelected 属性?

如何通过仅更改高度而不更改宽度来调整 UILabel 的大小?

从 C# 到 Objective C 会有多大的飞跃

Objective-c中release和dealloc的区别

Xcode 是否支持区域?

iOS performSelectorOnMainThread 有多个参数

在 UILabel.attributedText *not* 蓝色和 *not* 下划线

Objective-C 浮点舍入

Objective-C:集合枚举块中的 continue继续?

适用于 ios 的 360° 全景库

如何在使用 React Native 时实现 SSL 证书固定

UITableView - Select 了哪一行?

iOS 7 - 键盘动画

StoryBoard 助理编辑器停止显示相关文件

使用 NSLog 打印 NSData

Cocoapods ld:找不到-lPods-Projectname 的库

允许用户从 UILabel 中 Select 文本进行复制

如何在 Xcode 7 中启用_BITCODE?

如何判断 WkWebView 是否在 Objective-C 中完成加载?