核心数据privateQueue在访问关系时执行BlockAndWait死锁

许多论坛已经讨论过这个主题,但我仍然无法完全理解performBlockAndWait实际工作原理。 根据我的理解, context.performBlockAndWait(block: () -> Void)将在阻塞调用程序线程的同时在其自己的队列中执行该块。 文件说:

您将“标准”消息分组以发送到块中的上下文以传递给这些方法之一。

什么是“标准”消息? 它还说:

基于队列的托管对象上下文的Setter方法是线程安全的。 您可以直接在任何线程上调用这些方法。

这是否意味着我可以设置在performBlock * API之外的上下文的performBlock * API内获取的托管对象的属性?

根据我的理解,在具有并发类型的上下文中调用performBlockAndWait(block: () -> Void) .MainQueueConcurrencyType将在从主线程调用时永远创建死锁并阻止UI。 但在我的测试中,它没有造成任何僵局。

我认为它应该创建死锁的原因是,performBlockAndWait将首先阻塞调用程序线程,然后在其自己的线程上执行该块。 由于上下文必须执行其块的线程与已被阻塞的调用者线程相同,因此它将永远无法执行其块并且线程将永远被阻塞。

但是我在一些奇怪的场景中遇到了僵局。 我有以下测试代码:

 @IBAction func fetchAllStudentsOfDepartment(sender: AnyObject) { let entity = NSEntityDescription.entityForName("Department", inManagedObjectContext: privateContext) let request = NSFetchRequest() request.entity = entity request.relationshipKeyPathsForPrefetching = ["students"] var department: Department? privateContext.performBlockAndWait { () -> Void in department = try! self.privateContext.executeFetchRequest(request).first as? Department print(department?.name) guard let students = department?.students?.allObjects as? [Student] else { return } for student in students { print(student.firstName) } } } @IBAction func fetchDepartment(sender: AnyObject) { let entity = NSEntityDescription.entityForName("Department", inManagedObjectContext: privateContext) let request = NSFetchRequest() request.entity = entity privateContext.performBlockAndWait { () -> Void in let department = try! self.privateContext.executeFetchRequest(request).first as? Department print(department?.name) } privateContext.performBlockAndWait { () -> Void in let department = try! self.privateContext.executeFetchRequest(request).first as? Department print(department?.name) } } 

请注意,我在测试代码中的fetchDepartment方法中不小心粘贴了performBlockAndWait两次。

  • 如果我没有调用fetchAllStudentsOfDepartment方法,它不会造成任何死锁。 但是一旦我调用fetchAllStudentsOfDepartment ,任何对fetchDepartment方法的调用fetchDepartment永久阻止UI。
  • 如果我在fetchAllStudentsOfDepartment方法中删除print(student.firstName) ,那么它不会阻止。 这意味着,只有在我访问关系的属性时才会阻止UI。
  • privateContextconcurrencyType设置为.PrivateQueueConcurrencyType 。 仅当privateContextparentContextconcurrencyType设置为.MainQueueConcurrencyType时,上述代码才会阻止UI。

    我已经使用其他.xcdatamodel测试了相同的代码,我现在确信它只会阻止访问关系的属性。 我目前的.xcdatamodel看起来像: 数据模型

请原谅我,如果信息是无关紧要的,但我只是在花了8个小时后才分享我的所有观察结果。 当UI被阻止时,我可以发布我的线程堆栈。 总而言之,我有三个问题:

  1. 什么是“标准”消息?
  2. 我们可以设置在performBlock *之外的上下文的performBlock * API内获取的托管对象的属性吗?
  3. 为什么performBlockAndWait在我的测试代码中行为不端并导致UI阻塞。

测试代码:您可以从这里下载测试代码。

  1. 标准消息是旧的Objective-C术语。 这意味着你应该在一个ManagedObjectContext上执行所有常规方法调用,并在performBlockperformBlockAndWait中执行它的子performBlockAndWait 。 在块外部的私有上下文中允许的唯一调用是initsetParentContext 。 其他任何事情都应该在一个街区完成。

  2. 不可以。只能在该私有上下文的队列中访问从私有上下文获取的任何托管对象。 从另一个队列访问(读取或写入)违反了线程限制规则。

  3. 您遇到阻塞问题的原因是因为您有两个级别的“mainQueue”上下文,这就是“超越”队列系统。 这是流程:

    • 您在主队列上创建一个上下文,然后将其创建为另一个主队列上下文的子级。
    • 您创建该第二层主队列上下文的私有子级
    • 您以这样的方式访问该专用队列上下文,即它尝试在当前已加载到主队列上下文中的对象中发生故障。

由于主队列上下文的两个级别,它导致死锁,通常队列系统会看到潜在的死锁并避免死锁。

您可以通过将mainContext变量更改为:

 lazy var mainContext: NSManagedObjectContext = { let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate return appDelegate!.managedObjectContext } 

而你的问题就消失了,因为队列系统会看到阻止并避开它。 您甚至可以通过在performBlockAndWait()放置一个断点来查看发生的情况,并看到您仍在主队列中。

最后,没有理由在父/子设计中有两级主队列上下文。 如果有的话,这是一个很好的论据,不这样做。

更新

我错过了你在appDelegate中修改了模板代码并将整体上下文转换为私有上下文。

每个vc具有主MOC的模式抛弃了Core Data的许多好处。 虽然在顶部有一个私有和一个主MOC(整个应用程序存在,而不仅仅是一个VC)是一个有效的设计,如果你从主队列执行这样的performBlockAndWait它将无法工作。

我不建议您在阻止整个应用程序时使用主队列中的performBlockAndWait 。 只应在调用主队列(或者可能是一个背景到另一个背景)时使用performBlockAndWait

  1. 什么是“标准”消息?

发送到托管对象上下文或任何托管对象的任何消息。 请注意,文档继续澄清……

 There are two exceptions: * Setter methods on queue-based managed object contexts are thread-safe. You can invoke these methods directly on any thread. * If your code is executing on the main thread, you can invoke methods on the main queue style contexts directly instead of using the block based API. 

因此,必须从performBlock内部调用除MOC上的setter方法之外的任何内容。 可以从主线程调用具有NSMainQueueConcurrencyType的MOC上的任何方法,而不将其包装在performBlock

  1. 我们可以设置在performBlock *之外的上下文的performBlock * API内获取的托管对象的属性吗?

否。必须在托管对象所在的托管对象上下文中对托管对象的任何访问权限进行保护。 请注意驻留在从主队列访问的主队列MOC中的托管对象的exception。

  1. 为什么performBlockAndWait在我的测试代码中行为不端并导致UI阻塞。

这不是行为不端。 performBlockAndWait是可重入的,但仅在已处理performBlock[AndWait]调用时。

除非没有其他选项,否则不应使用performBlockAndWait 。 嵌套上下文尤其成问题。

请改用performBlock