15

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.

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.

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?

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.

Community
  • 1
  • 1
rene
  • 1,975
  • 21
  • 33

2 Answers2

20

Update: As @adib points out in a comment, the approach to serialized managed object context (MOC) 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.


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.

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.

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.

Ben Butterworth
  • 22,056
  • 10
  • 114
  • 167
Caleb
  • 124,013
  • 19
  • 183
  • 272
  • 1
    Great answer, but in the linked thread, bbum's confusion seems to imply that the MOC object itself cannot cross thread boundaries, even if access is serialised. Ben specifically states this, so what are we expected to do? – Aidan Steele Oct 11 '11 at 03:03
  • 2
    @SedateAlien, early in his answer, Ben points to the note in the [documentation](http://tinyurl.com/6dlg75j) that says: ***Note:** You can use threads, serial operation queues, or dispatch queues for concurrency. For the sake of conciseness, this article uses “thread” throughout to refer to any of these."* So, in the remainder of his answer, or in the documentation, you can substitute *serial dispatch queue* for *thread*. Again, the essential thing is to avoid using the same context for different things at the same time. Any of the three mechanisms mentioned can be used to achieve that. – Caleb Oct 11 '11 at 03:18
  • So I _must create_ the MOC on said serial queue? If I create the MOC object off-queue but only use it on-queue, is that acceptable or asking for trouble? – Aidan Steele Oct 11 '11 at 03:21
  • @SedateAlien, see Chris Hanson's comment, too. He's saying that you should create a MOC on the thread that uses it because the behavior of the MOC depends on the thread: a MOC created on the main thread will expect different things than a MOC created on a background thread. I believe that doesn't apply in the case of a serial dispatch queue, though... the main thing that GCD does is to maintain a pool of worker threads that are interchangeable so that a task can be dispatched to any available thread. – Caleb Oct 11 '11 at 03:26
  • @SedateAlien, another choice line from Ben's answer: *Although queues are not bound to specific threads, if you create a MOC within the context of a queue the right things will happen.* To answer your question, I think the safe course of action would be to create the MOC in the serial dispatch queue. – Caleb Oct 11 '11 at 03:31
  • 1
    Thanks for all your help. I suppose this is a case of a little knowledge being a dangerous thing: I knew that the MOC was aware of the thread it was instantiated on and knew that GCD employed thread pools and couldn't imagine how they could work together, but now I accept that it will work. Thanks again. – Aidan Steele Oct 11 '11 at 03:38
  • 1
    @Caleb, thanks for the answer and the link with more detailed explanation. It helps for sure. I think part of my confusion is that I get the sense from the documentation that it is more than a matter of just avoiding simultaneous modifications to the context -- that there are other deep and mysterious things going on. Your answer helps, and I will definitely try to stick with Ben's recommendation and have respect for the complexity of the matter. – rene Oct 11 '11 at 03:56
  • 1
    @Caleb, thanks a lot! This is actually starting to make a bit of sense to me. I had not given enough attention to the distinction between serial versus concurrent dispatch queues. Your short answer clarifies even more. – rene Oct 12 '11 at 13:41
  • This answer is no longer true as of iOS 9 / OS X 10.11 – *thread confinement* MOC is deprecated and you're left with the main queue or the MOC's private queue. In both cases you shouldn't create/use your own dispatch queue to access Core Data objects. – adib Nov 21 '15 at 13:41
  • Good info, @adib -- thanks for pointing that out. I've added an update at the top of the answer and left the original because it answers the question in the context in which it was asked. – Caleb Nov 22 '15 at 17:23
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.

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.

tsnorri
  • 1,966
  • 5
  • 21
  • 29