Search code examples
iosmultithreadingcore-dataconcurrencynsmanagedobjectcontext

Multiple NSManagedObjectContexts - preventing race conditions and deadlocks


I've read a fair amount of blogs on background core data processes, but I'm no closer to understanding how best to manage many BG Core Data tasks firing all at once and notifying back to the main thread MOC at an undefined time.

I'm aware you are supposed to have 1 NSManagedObjectContext per NSThread and that by subscribing to NSManagedObjectContextDidSaveNotification along with using [context performBlock I get some nice asynchronous task completion.

That said, I'm running a lot of tasks asynchronously, I don't 100% know when some will overlap and I have observed race conditions in the form of...

  • BG MOC 1 sets off on it's task
  • BG MOC 2 sets off on it's task
  • BG MOC 2 completes task and the save notification is sent
  • BG MOC 1 completes task and subsequently wipes away BG MOC 2s changes

My overall Question is how do I solve race conditions in multiple MOC?

  1. If the correct behaviour is to have 1 MOC per thread.. Can I create an NSThread ivar and put all of my Core Data work onto it? That way I can have one MOC that works synchronously with itself?

  2. I've read an NSLock could be the solution to avoid certain code being accessed from multiple threads at the same time.. but I don't know what I'm supposed to be locking? The Save Context method? The persistent store (seems to make multithreading pointless)?

  3. Finally, can I tag / number / name my MOCs? that way if I know other tasks are running, I can store the notifications and process them in the order they were instantiated to ensure no data is overwritten?


Solution

    1. one MOC per thread is recommended. There are exceptions but that general rule still holds. Don't create NSThread objects. Just don't. Too much pain. Instead use blocks or NSOperation instances. They are easier to grok and protect you from a lot of pain.

    2. Do not use locks with Core Data. Core Data does its own locking when used properly and if you throw locks around you are going to cause issues. Ideally you should never need to call lock yourself in modern Objective-C.

    3. You cannot name the MOCs other than having ivar or property references to them. You shouldn't need to either.

    The cleanest way to do multi-threading with Core Data is as follows:

    • You have a main thread/UI MOC. That is your single source of truth. Your UI feeds from it and writes to it.
    • Any background process is done in a NSOperation or similar construct. You create a MOC inside of this construct and it is a child context to the main context.
    • When you save the child the changes will be merged to the parent (which is the UI MOC).
    • Set the merge policy that is appropriate on your main MOC. If you think you need different policies for different situations then you should rework how you are doing things.

    Ideally each background process should be a silo of data that can operate on its own without colliding with another process. If you have a collision then that is a merge issue that you need to solve in your business logic.

    If you have a situation that two background operations are going to hit the same piece of data then you should run them in sequence, not parallel. Parallel edits to the same data is pain waiting to happen, don't do it.

    You can control whether things are sequential or in parallel through the use of NSOperationQueue instances.

    Follow those rules and you won't have race conditions or deadlocks.