访问关系时核心数据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
。
- 如果我没有调用它不会造成任何死锁
fetchAllStudentsOfDepartment
方法。但是一旦我打电话
fetchAllStudentsOfDepartment
,任何对 fetchDepartment
方法的调用
永远阻止 UI。
- 如果我在
fetchAllStudentsOfDepartment
方法中删除 print(student.firstName)
,那么它不会阻塞。这意味着,只有当我访问关系的 属性. 时,它才会阻止 UI
privateContext
已将 concurrencyType
设置为 .PrivateQueueConcurrencyType
。仅当 privateContext
的 parentContext
将 concurrencyType
设置为 .MainQueueConcurrencyType
.
时,以上代码块 UI
我也用其他 .xcdatamodel
测试了相同的代码,现在我确信它只会在访问关系的 属性 时阻塞。我当前的 .xcdatamodel
看起来像:
请原谅我的信息是否无关,但我只是在花了大约 8 个小时后分享我的所有观察结果。当 UI 被阻塞时,我可以 post 我的线程堆栈。总结一下,我有三个问题:
- "standard" 消息是什么?
- 我们可以设置在
performBlock
* API 内部获取的托管对象的属性吗?performBlock
*?
- 为什么
performBlockAndWait
行为不当并导致 UI 在我的测试代码中阻塞。
测试代码: 您可以从 here 下载测试代码。
标准消息是旧的 Objective-C 行话。这意味着您应该对 performBlock
或 performBlockAndWait
中的 ManagedObjectContext 及其子 ManagedObjects 执行所有常规方法调用。在块外的私有上下文中唯一允许的调用是 init
和 setParentContext
。其他任何事情都应该在一个块中完成。
没有。从私有上下文中获取的任何托管对象只能在该私有上下文的队列中访问。从另一个队列访问(读取或写入)违反了线程限制规则。
您遇到阻塞问题的原因是因为您有两级 "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 主队列(或一个背景到另一个背景)时使用。
- 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
.
中
- Can we set properties of a managed object which is fetched inside performBlock* API of a context outside performBlock*?
没有。对托管对象的任何访问都必须在托管对象所在的托管对象上下文中的 performBlock
内部受到保护。请注意驻留在从主队列访问的 main-queue MOC 中的托管对象的异常。
- Why performBlockAndWait is misbehaving and causing UI block in my test code.
这不是行为不端。 performBlockAndWait
是可重入的,但仅当已经处理了 performBlock[AndWait]
调用时。
除非别无选择,否则永远不要使用 performBlockAndWait
。嵌套上下文尤其有问题。
改用performBlock
。
这个话题已经在很多论坛上讨论过,但我仍然不能完全理解 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
。
- 如果我没有调用它不会造成任何死锁
fetchAllStudentsOfDepartment
方法。但是一旦我打电话fetchAllStudentsOfDepartment
,任何对fetchDepartment
方法的调用 永远阻止 UI。 - 如果我在
fetchAllStudentsOfDepartment
方法中删除print(student.firstName)
,那么它不会阻塞。这意味着,只有当我访问关系的 属性. 时,它才会阻止 UI
时,以上代码块 UIprivateContext
已将concurrencyType
设置为.PrivateQueueConcurrencyType
。仅当privateContext
的parentContext
将concurrencyType
设置为.MainQueueConcurrencyType
.我也用其他
.xcdatamodel
测试了相同的代码,现在我确信它只会在访问关系的 属性 时阻塞。我当前的.xcdatamodel
看起来像:
请原谅我的信息是否无关,但我只是在花了大约 8 个小时后分享我的所有观察结果。当 UI 被阻塞时,我可以 post 我的线程堆栈。总结一下,我有三个问题:
- "standard" 消息是什么?
- 我们可以设置在
performBlock
* API 内部获取的托管对象的属性吗?performBlock
*? - 为什么
performBlockAndWait
行为不当并导致 UI 在我的测试代码中阻塞。
测试代码: 您可以从 here 下载测试代码。
标准消息是旧的 Objective-C 行话。这意味着您应该对
performBlock
或performBlockAndWait
中的 ManagedObjectContext 及其子 ManagedObjects 执行所有常规方法调用。在块外的私有上下文中唯一允许的调用是init
和setParentContext
。其他任何事情都应该在一个块中完成。没有。从私有上下文中获取的任何托管对象只能在该私有上下文的队列中访问。从另一个队列访问(读取或写入)违反了线程限制规则。
您遇到阻塞问题的原因是因为您有两级 "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 主队列(或一个背景到另一个背景)时使用。
- 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
.
- Can we set properties of a managed object which is fetched inside performBlock* API of a context outside performBlock*?
没有。对托管对象的任何访问都必须在托管对象所在的托管对象上下文中的 performBlock
内部受到保护。请注意驻留在从主队列访问的 main-queue MOC 中的托管对象的异常。
- Why performBlockAndWait is misbehaving and causing UI block in my test code.
这不是行为不端。 performBlockAndWait
是可重入的,但仅当已经处理了 performBlock[AndWait]
调用时。
除非别无选择,否则永远不要使用 performBlockAndWait
。嵌套上下文尤其有问题。
改用performBlock
。