1

I've been experimenting with the core data setup recommended by Marcus Zarra's core data book. The setup entails two managed object contexts. A parent moc with concurrency type private. And a child main context. The stated reasoning behind this is that the main core data context can have super fast reads/writes since changes on the main context (on main queue) are propagated up to the parent, and not to disk.

However Zarra's core data initialization method sets up each context on the same thread. Since performblock* methods are executed on the same queue that the managedobjectcontext was created on, it sounds like all core data reads/writes are going to happen on the main queue. Wouldn't this stack be better served by setting up the private context on a background thread?

That thought led me to write code (inspired by code in Zarra's book) resembling the following:

    __block NSManagedObjectContext *private = nil;

    dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    NSPersistentStoreCoordinator *psc = nil;
    psc = [self persistentStoreCoordinator];

    NSUInteger type = NSPrivateQueueConcurrencyType;
    private = [[NSManagedObjectContext alloc] initWithConcurrencyType:type];
    [private setPersistentStoreCoordinator:psc];
    });

    NSManagedObjectContext *moc = nil;
    type = NSMainQueueConcurrencyType;
    moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:type];
    [moc setParentContext:private];

    _mainManagedObjectContext = moc;
    _backgroundObjectContext = private;

   ...

When I set up my core data stack like this, I end up with a deadlock coming from performBlockAndWait as the main thread waits for itself to free up in order to execute that block of work...ad infinitum. Strangely, the queue i am attempting to perform work on is not just any global queue--it's the main thread. For some reason, dispatch_sync with one of the built-in global queues (or for a dispatch_queue i create myself) does not guarantee that the thread used for the chosen block of work will be a thread other than the main thread. Is there anyway other than going lower than gcd (e.g. using nsthread etc...) to guarantee that the block will be executed on a thread other than the main thread.

2 Answers2

0

You seem to be confused about which dispatch function you're using. In your code it's "_sync", but in the prose it's "_async". The purpose of dispatch_sync is to block the current thread until something completes, so this would be expected for how you wrote it. Try dispatch_async instead.

Catfish_Man
  • 41,261
  • 11
  • 67
  • 84
  • That was a typo. Thanks for pointing it out. My questions still remain unanswered. – Peter Zakin Aug 12 '14 at 00:20
  • I don't see how it's unanswered. Using dispatch_sync on the main thread is *explicitly* saying "I want to block the main thread". – Catfish_Man Aug 12 '14 at 01:39
  • blocking the main thread does not necessarily entail a deadlock. A deadlock happens when a thread waits for itself. See http://stackoverflow.com/questions/10984732/why-cant-we-use-a-dispatch-sync-on-the-current-queue. The question here is why dispatch_sync to a global queue dispatches work on the main thread. – Peter Zakin Aug 12 '14 at 01:46
  • 2
    dispatch_sync will reuse the thread it's called on as an optimization. It does this because the result is indistinguishable: the current thread is blocked regardless. – Catfish_Man Aug 12 '14 at 04:07
  • Yeah i just discovered that note in the documentation. The problem here is that I'm trying to create the managed object context on a background thread so that when i call performblock* the block will be executed on a background thread. – Peter Zakin Aug 12 '14 at 04:30
  • Ah. Yeah, inspecting thread identity like that is a violation of libdispatch's invariants, I believe (which is why it believes it can do that safely) :/ CoreData should really be using another mechanism to decide. – Catfish_Man Aug 12 '14 at 04:34
0

Since performblock* methods are executed on the same queue that the managedobjectcontext was created on,

That statement is just plain wrong.

The only way to use performBlock is with a private queue or the main queue, and each call to performBlock will enqueue the block on the appropriate queue, regardless of which thread/queue was involved in the creation of the MOC.

Now, your deadlock is caused by using performBlockAndWait which has different behavior than performBlock. performBlockAndWait will cause the calling thread to wait until the block can be executed synchronously with respect to the MOCs dispatch queue.

Also, there is no guarantee at all on which thread certain blocks of code will run... except in the case of the "main" queue.

Finally, one should rarely, if ever use performBlockAndWait. Yes, it is re-entrant, but it can also cause deadlocks. It should be used only in specific cases where you know for certain that the code being called can not possibly be called from any other block. Those should be very rare, if your code is asynchronous, which it should be.

Lance
  • 8,872
  • 2
  • 36
  • 47
Jody Hagins
  • 27,943
  • 6
  • 58
  • 87
  • The documentation states: "Core Data uses thread (or serialized queue) confinement to protect managed objects and managed object contexts (see “Concurrency with Core Data”). A consequence of this is that a context assumes the default owner is the thread or queue that allocated it—this is determined by the thread that calls its init method. You should not, therefore, initialize a context on one thread then pass it to a different thread." Doesn't that suggest that performblock* methods will execute on the thread that created the managed object context? – Peter Zakin Aug 12 '14 at 16:30
  • You're right. Thanks! My confusion is echoed here http://stackoverflow.com/questions/23231781/creating-a-nsmanagedobjectcontext-on-a-private-background-queue-how-to-do?rq=1 – Peter Zakin Aug 12 '14 at 16:56