GCD调度队列是否足以将Core Data上下文限制在单个线程中

时间:2022-06-29 21:04:52

I'm beginning to think the answer to my question is 'No', but I'm still confused and uncertain about this. So please confirm. I've already learned the need to be careful when using Core Data with multiple threads. NSManagedObjectContext objects must not cross thread boundaries. Being a newbie with both threads and Core Data, I happily found that GCD should make some of this easier.

我开始认为我的问题的答案是'不',但我仍然对此感到困惑和不确定。所以请确认。我已经了解了在使用多线程的Core Data时需要小心。 NSManagedObjectContext对象不得跨越线程边界。作为一个同时拥有线程和核心数据的新手,我很高兴地发现GCD应该可以让其中的一些变得更容易。

Naively perhaps, I then thought I would simply create a dedicated GCD dispatch queue for dealing with Core Data (or even, if needed, have multiple dispatch queues each with its own core data context). That would have been simple.

或许,我想我会简单地创建一个专用的GCD调度队列来处理核心数据(如果需要,甚至可以有多个调度队列,每个队列都有自己的核心数据上下文)。那会很简单。

But now I realize that one big advantage of GCD dispatch queues is that it manages and makes use of multiple threads as needed. So - if I understand this right - tasks I hand off to one and the same dispatch queue, could end up running in different threads, potentially handing off a core data context from one thread to another, and having things go wrong. Is that right?

但现在我意识到GCD调度队列的一大优势是它可以根据需要管理和使用多个线程。所以 - 如果我理解这一点 - 我交给同一个调度队列的任务最终会在不同的线程中运行,可能会将核心数据上下文从一个线程切换到另一个线程,并且出现问题。是对的吗?

I've read many related questions and answers, for example Core Data and threads / Grand Central Dispatch, but I remain somewhat confused. The accepted answer to that question, using GCD queues, does ensure that a new context is created on each thread, but does not point out the necessity of doing this. Another answer says "You could execute all CoreData work on a queue named com.yourcompany.appname.dataaccess" seeming to imply that as long as the Core Data work is confined to one GCD dispatch queue, then all is OK. Maybe it is not.

我已经阅读了许多相关的问题和答案,例如Core Data和threads / Grand Central Dispatch,但我仍然有些困惑。使用GCD队列接受该问题的答案确实确保在每个线程上创建新的上下文,但没有指出执行此操作的必要性。另一个答案是“您可以在名为com.yourcompany.appname.dataaccess的队列上执行所有CoreData工作”,这似乎意味着只要Core Data工作仅限于一个GCD调度队列,那么一切正常。也许不是。

2 个解决方案

#1


20  

Update: As @adib points out in a comment, the approach to serialized managed object context access has changed in iOS 9 and MacOS X 10.11. NSConfinementConcurrencyType, the thread confinement strategy, is now deprecated in favor of NSPrivateQueueConcurrencyType and NSMainQueueConcurrencyType. In other words, stop using threads for concurrent access to Core Data objects and instead start using GCD. You should use either the main dispatch queue or the one associated with the MOC, depending on how you configure the MOC, and not a queue of your own creation. This is easy to do using NSManagedObject's -performBlock: or -performBlockAndWait: methods.

更新:正如@a​​dib在评论中指出的那样,在iOS 9和MacOS X 10.11中,序列化托管对象上下文访问的方法已经改变。 NSConfinementConcurrencyType是线程限制策略,现在不推荐使用NSPrivateQueueConcurrencyType和NSMainQueueConcurrencyType。换句话说,停止使用线程并发访问Core Data对象,而是开始使用GCD。您应该使用主调度队列或与MOC关联的队列,具体取决于您配置MOC的方式,而不是您自己创建的队列。使用NSManagedObject的-performBlock:或-performBlockAndWait:方法很容易做到这一点。


Short answer: Using a serial dispatch queue can provide serialized access to a managed object context, and that's an acceptable way to implement the "thread confinement" strategy even though GCD may actually employ multiple threads.

简短回答:使用串行调度队列可以提供对托管对象上下文的序列化访问,这是实现“线程限制”策略的可接受方式,即使GCD实际上可能使用多个线程。

Longer answer:

The accepted answer to that question, using GCD queues, does ensure that a new context is created on each thread, but does not point out the necessity of doing this.

使用GCD队列接受该问题的答案确实确保在每个线程上创建新的上下文,但没有指出执行此操作的必要性。

The big thing you need to remember is that you must avoid modifying the managed object context from two different threads at the same time. That could put the context into an inconsistent state, and nothing good can come of that. So, the kind of dispatch queue that you use is important: a concurrent dispatch queue would allow multiple tasks to proceed simulaneously, and if they both use the same context you'll be in trouble. If you use a serial dispatch queue, on the other hand, two or more tasks might execute on different threads, but the tasks will be executed in order, and only one task will run at a time. This is very similar to running all the tasks on the same thread, at least as far as maintaining the context's consistency goes.

您需要记住的重要一点是,您必须避免同时从两个不同的线程修改托管对象上下文。这可能会使上下文处于不一致状态,并且没有任何好处。因此,您使用的调度队列类型很重要:并发调度队列将允许多个任务同时进行,如果它们都使用相同的上下文,您将遇到麻烦。另一方面,如果使用串行调度队列,则可能在不同的线程上执行两个或多个任务,但任务将按顺序执行,并且一次只能运行一个任务。这与在同一个线程上运行所有任务非常相似,至少就维护上下文的一致性而言。

See this question and answer for a much more detailed explanation.

请参阅此问题和答案以获得更详细的解释。

This is how Core Data has always worked. The Concurrency with Core Data section of the Core Data Programming Guide gives advice on how to proceed if you do decide to use a single context in multiple threads. It talks mainly about the need to be very careful to lock the context any time you access it. The point of all that locking, though, is to ensure that two or more threads don't try to use the context simultaneously. Using a serialized dispatch queue achieves the same goal: because only one task in the queue executes at a time, there's no chance that two or more tasks will try to use the context at the same time.

这就是Core Data一直以来的工作方式。如果您决定在多个线程中使用单个上下文,则“核心数据编程指南”中的“核心数据并发”部分提供有关如何继续的建议。它主要讨论在您访问上下文时需要非常小心地锁定上下文。但是,所有锁定的关键是要确保两个或多个线程不会同时尝试使用上下文。使用序列化调度队列实现了相同的目标:因为队列中一次只执行一个任务,所以两个或多个任务不可能同时尝试使用上下文。

#2


0  

AFAIK you're correct; GCD doesn't make guarantees about the thread in which the queue is run. Blocks and function calls sent to the queue will be run one at a time, but if Core Data does something with the current thread, e.g. installs a run loop source or observer, things probably won't work as intended.

AFAIK你是对的; GCD不保证运行队列的线程。发送到队列的块和函数调用将一次运行一个,但是如果Core Data对当前线程执行某些操作,例如,安装一个运行循环源或观察者,事情可能无法按预期工作。

However, on Mac OS X 10.7, NSManagedObjectContext can be set to run on the main thread, on a separate thread, or in a private queue.

但是,在Mac OS X 10.7上,可以将NSManagedObjectContext设置为在主线程,单独线程或专用队列中运行。

#1


20  

Update: As @adib points out in a comment, the approach to serialized managed object context access has changed in iOS 9 and MacOS X 10.11. NSConfinementConcurrencyType, the thread confinement strategy, is now deprecated in favor of NSPrivateQueueConcurrencyType and NSMainQueueConcurrencyType. In other words, stop using threads for concurrent access to Core Data objects and instead start using GCD. You should use either the main dispatch queue or the one associated with the MOC, depending on how you configure the MOC, and not a queue of your own creation. This is easy to do using NSManagedObject's -performBlock: or -performBlockAndWait: methods.

更新:正如@a​​dib在评论中指出的那样,在iOS 9和MacOS X 10.11中,序列化托管对象上下文访问的方法已经改变。 NSConfinementConcurrencyType是线程限制策略,现在不推荐使用NSPrivateQueueConcurrencyType和NSMainQueueConcurrencyType。换句话说,停止使用线程并发访问Core Data对象,而是开始使用GCD。您应该使用主调度队列或与MOC关联的队列,具体取决于您配置MOC的方式,而不是您自己创建的队列。使用NSManagedObject的-performBlock:或-performBlockAndWait:方法很容易做到这一点。


Short answer: Using a serial dispatch queue can provide serialized access to a managed object context, and that's an acceptable way to implement the "thread confinement" strategy even though GCD may actually employ multiple threads.

简短回答:使用串行调度队列可以提供对托管对象上下文的序列化访问,这是实现“线程限制”策略的可接受方式,即使GCD实际上可能使用多个线程。

Longer answer:

The accepted answer to that question, using GCD queues, does ensure that a new context is created on each thread, but does not point out the necessity of doing this.

使用GCD队列接受该问题的答案确实确保在每个线程上创建新的上下文,但没有指出执行此操作的必要性。

The big thing you need to remember is that you must avoid modifying the managed object context from two different threads at the same time. That could put the context into an inconsistent state, and nothing good can come of that. So, the kind of dispatch queue that you use is important: a concurrent dispatch queue would allow multiple tasks to proceed simulaneously, and if they both use the same context you'll be in trouble. If you use a serial dispatch queue, on the other hand, two or more tasks might execute on different threads, but the tasks will be executed in order, and only one task will run at a time. This is very similar to running all the tasks on the same thread, at least as far as maintaining the context's consistency goes.

您需要记住的重要一点是,您必须避免同时从两个不同的线程修改托管对象上下文。这可能会使上下文处于不一致状态,并且没有任何好处。因此,您使用的调度队列类型很重要:并发调度队列将允许多个任务同时进行,如果它们都使用相同的上下文,您将遇到麻烦。另一方面,如果使用串行调度队列,则可能在不同的线程上执行两个或多个任务,但任务将按顺序执行,并且一次只能运行一个任务。这与在同一个线程上运行所有任务非常相似,至少就维护上下文的一致性而言。

See this question and answer for a much more detailed explanation.

请参阅此问题和答案以获得更详细的解释。

This is how Core Data has always worked. The Concurrency with Core Data section of the Core Data Programming Guide gives advice on how to proceed if you do decide to use a single context in multiple threads. It talks mainly about the need to be very careful to lock the context any time you access it. The point of all that locking, though, is to ensure that two or more threads don't try to use the context simultaneously. Using a serialized dispatch queue achieves the same goal: because only one task in the queue executes at a time, there's no chance that two or more tasks will try to use the context at the same time.

这就是Core Data一直以来的工作方式。如果您决定在多个线程中使用单个上下文,则“核心数据编程指南”中的“核心数据并发”部分提供有关如何继续的建议。它主要讨论在您访问上下文时需要非常小心地锁定上下文。但是,所有锁定的关键是要确保两个或多个线程不会同时尝试使用上下文。使用序列化调度队列实现了相同的目标:因为队列中一次只执行一个任务,所以两个或多个任务不可能同时尝试使用上下文。

#2


0  

AFAIK you're correct; GCD doesn't make guarantees about the thread in which the queue is run. Blocks and function calls sent to the queue will be run one at a time, but if Core Data does something with the current thread, e.g. installs a run loop source or observer, things probably won't work as intended.

AFAIK你是对的; GCD不保证运行队列的线程。发送到队列的块和函数调用将一次运行一个,但是如果Core Data对当前线程执行某些操作,例如,安装一个运行循环源或观察者,事情可能无法按预期工作。

However, on Mac OS X 10.7, NSManagedObjectContext can be set to run on the main thread, on a separate thread, or in a private queue.

但是,在Mac OS X 10.7上,可以将NSManagedObjectContext设置为在主线程,单独线程或专用队列中运行。