访问关系时核心数据privateQueue performBlockAndWait死锁

Core Data privateQueue performBlockAndWait deadlock while accessing relationship

这个话题已经在很多论坛上讨论过,但我仍然不能完全理解 performBlockAndWait 是如何工作的。根据我的理解,context.performBlockAndWait(block: () -> Void) 将在阻塞调用者线程的同时在自己的队列中执行块。 Documentation 说:

You group “standard” messages to send to the context within a block to pass to one of these methods.

"standard" 消息是什么?它还说:

Setter methods on queue-based managed object contexts are thread-safe. You can invoke these methods directly on any thread.

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

根据我的理解,在具有并发类型 .MainQueueConcurrencyType 的上下文中调用 performBlockAndWait(block: () -> Void) 将创建死锁并在从主线程调用时永远阻塞 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

请原谅我的信息是否无关,但我只是在花了大约 8 个小时后分享我的所有观察结果。当 UI 被阻塞时,我可以 post 我的线程堆栈。总结一下,我有三个问题:

  1. "standard" 消息是什么?
  2. 我们可以设置在 performBlock* API 内部获取的托管对象的属性吗?performBlock*?
  3. 为什么 performBlockAndWait 行为不当并导致 UI 在我的测试代码中阻塞。

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

  1. 标准消息是旧的 Objective-C 行话。这意味着您应该对 performBlockperformBlockAndWait 中的 ManagedObjectContext 及其子 ManagedObjects 执行所有常规方法调用。在块外的私有上下文中唯一允许的调用是 initsetParentContext。其他任何事情都应该在一个块中完成。

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

  3. 您遇到阻塞问题的原因是因为您有两级 "mainQueue" 上下文,即 "outsmarting" 队列系统。这是流程:

    • 您在主队列上创建一个上下文,然后将其创建为另一个主队列上下文的子项。
    • 您创建了该第二层主队列上下文的私有子项
    • 您访问该专用队列上下文的方式试图在当前已加载到主队列上下文中的对象中出错。

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

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

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

并且您的问题消失了,因为队列系统会看到该块并避免它。您甚至可以通过在 performBlockAndWait() 中放置一个断点来看到这种情况的发生,并看到您仍在主队列中。

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

更新

我错过了您更改了 appDelegate 中的模板代码并将整个上下文变成了私有上下文。

每个 vc 都有一个主 MOC 的模式抛弃了 Core Data 的很多好处。虽然在顶部有一个 private 和一个主 MOC(它存在于整个应用程序,而不仅仅是一个 VC)是一个有效的设计,但如果你从主队列。

我不建议使用主队列中的 performBlockAndWait,因为您会阻塞整个应用程序。 performBlockAndWait 只应在调用 TO 主队列(或一个背景到另一个背景)时使用。

  1. What are the "standard" messages?

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

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. Can we set properties of a managed object which is fetched inside performBlock* API of a context outside performBlock*?

没有。对托管对象的任何访问都必须在托管对象所在的托管对象上下文中的 performBlock 内部受到保护。请注意驻留在从主队列访问的 main-queue MOC 中的托管对象的异常。

  1. Why performBlockAndWait is misbehaving and causing UI block in my test code.

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

除非别无选择,否则永远不要使用 performBlockAndWait。嵌套上下文尤其有问题。

改用performBlock