Does a Core Data parent ManagedObjectContext need to share a concurrency type with the child context?
Asked Answered
H

1

23

Can I set the parent context of my ManagedObjectContext to a ManagedObjectContext with a different concurrency type? For example:

backgroundManagedObjectContext_ = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[backgroundManagedObjectContext_ setPersistentStoreCoordinator:coordinator];
managedObjectContext_ = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[managedObjectContext_ setParentContext:backgroundManagedObjectContext_];

My goal is to (hopefully) get a fast save for managedObjectContext_ (as it only needs to save things to the parent in-memory context) and then have the backgroundManagedObjectContext_ do a save on its own queue. Unless I happen to need to do another save from the "foreground" queue before the background one saves my user should never see long save times.

I'm running into what look like deadlocks, but I'm not sure if they are related to this, or if my problem is elsewhere.


Details for the one place where I can more or less reliably produce the deadlock:

I have a managed object context for the main thread, it is backed by a managed object context with a private queue concurrency type, and that one has a persistent store.

I have a handful of entity types (about 5), each with one or two bi-directional relationships to another entity.

My app wants to use iCloud (I have that code dialed for these tests), so it can't ship with a pre-populated database, it needs to build it when it detects an empty one.

So when I see an empty database (this is checked on the main thread) I add around 4 or 8 of all but one of the entity types, and that last one gets about 100 (all of the adds happen on the main thread). The main thread does a saveContext. After that completes (with no errors) it asks the other managed object context to run a block that also does a saveContext. This saveContext is definitely a deadlock participant. This is also the ONLY stuff done with the "background" NSManagedObjectContext at all.

The exact control flow is a little murky after this, as a NSFetchedResultsController (all of a given entity type (which has ~3 members) with a simple sort, and a batch size of 20 or so) drive the next round of Core Data activity, but there is call from the TableViewController asking how many items it needs to manage, which is "how many results does the fetched results controller have". That call is the other side of the deadlock. (all this is in the main thread)

Hookah answered 7/12, 2011 at 23:27 Comment(1)
For sake of naming convention, I suggest you remove the "_" suffix in your code. Name contexts as childContext/parentContext or child/parent.Alsatian
D
36

From my experience, this is not necessary as long as both contexts implement either the NSMainQueueConcurrencyType or the NSPrivateQueueConcurrencyType. The important thing to remember is that when interacting with managed object contexts across multiple threads, all messages sent to the context must be sent via either -performBlock: or performBlockAndWait:.

In a recent project, I had a parent NSManagedObjectContext that backed an NSFetchedResultsController that was created with the NSMainQueueConcurrencyType. From there I created an NSManagedObjectContext with the NSPrivateQueueConcurrencyType and set the context with the NSMainQueueConcurrencyType as the parent. Now my child context could contain discardable edits when adding a new object that would eventually end up in the table view backed by the NSFetchedResultsController which is backed by the parent context. When I was ready to save my edits and bubble them up to the parent context, the code looked like this.

// Save the child context first
[childContext performBlock:^{
    NSError *error = nil;
    [childContext save:&error];

    // Save the changes on the main context
    [parentContext performBlock:^{
        NSError *parentError = nil;
        [parentContext save:&parentError];
    }];
}];

Now I can't say with certainty that this is the correct way to do this because the documentation on this is pretty sparse at the moment. It might be helpful if you could show some code that you believe is causing deadlocks. If you're in the developer program, you may want to check out the WWDC session videos regarding Core Data from this year. I believe there are two, one for Mac OS X and one for iOS, that basically touch on the same ideas but each contain unique information as well.

Dichromic answered 10/12, 2011 at 0:53 Comment(3)
Best & only answer with an hour left on the clock. I award you 50 points! (and I've watched one of the two WWDC videos, now that I know the other has some unique info I'll give that a watch too).Hookah
I think the only difference is that Stripes make parent context backing by private queue and child context backing by main queue. Yet @Mark you do the other way around. I think that makes the difference of deadlock or not. Apple seems to support Stripes' way in WWDC 2012 session 214.Alsatian
It shouldn't matter which type of context the parent or child is as long as they both don't use the old thread confinement model. It's more likely the that OP had two -performBlockAndWait:s waiting on one another.Dichromic

© 2022 - 2024 — McMap. All rights reserved.