performBlockAndWait creates deadlock
Asked Answered
J

2

6

I am writing a function that performs some CoreData stuff. I want the function to return only after all the CoreData operations have executed. The CoreData stuff involves creating an object in a background context, then doing some more stuff in the parent context:

+ (void) myFunction
    NSManagedObjectContext *backgroundContext = [DatabaseDelegate sharedDelegate].backgroundContext;

    [backgroundContext performBlockAndWait:^{
      MyObject *bla = create_my_object_in:backgroundContext;

      [backgroundContext obtainPermanentIDsForObjects:[[backgroundContext insertedObjects] allObjects] error:nil];
      [backgroundContext save:nil];

      [[DatabaseDelegate sharedDelegate].parent.managedObjectContext performBlockAndWait:^{
        [[DatabaseDelegate sharedDelegate].parent updateChangeCount:UIDocumentChangeDone];

        // Do some more stuff
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        [queue addOperation:someOperation];
      }];
    }];
   return;
}

I want the return to only happen after [queue addOperation:someOperation]. This seems to work most of the cases, but I have had one case when this function never returned. It seemed like it was deadlocked, and I suspect it is because of performBlockAndWait.

My questions are:

(1) Can someone explain why this deadlock occurs?

and

(2) What is the right way of achieving the same functionality? The requirement is that myFunction returns only after both blocks have been executed.

Thank you!

Jerri answered 19/9, 2012 at 18:23 Comment(0)
S
13

Let's imagine you are calling myFunction from the main thread. Let's imagine [DatabaseDelegate sharedDelegate].parent.managedObjectContext is scheduled on the main thread.

With [backgroundContext performBlockAndWait:] you are scheduling a block on the context private background queue. Blocking the main thread.

With [.parent.managedObjectContext performBlockAndWait:], you are scheduling a block on the main thread, blocking the private queue.

But the main thread is blocked already. So the block will never execute. And performBlockAndWait: will never returns.

Deadlock.

Use asynchronously scheduled blocks, with completion blocks.

Sheugh answered 19/9, 2012 at 18:37 Comment(3)
But my requirement is that myFunction only returns after the two blocks have executed. In essence, they rely on the CoreData objects being created. So if I use performBlock (asynchronously), then I don't have that guarantee.Jerri
If parent.managedObjectContext uses the main thread and myFunction is called from the main thread, you can take the stuff in parent.managedObjectContext performBlockAndWait out of both performBlockAndWait and execute it in myFunction body, right after the first performBlockAndWait.Sheugh
This answer is wrong. backgroundContext performBlockAndWait: makes no guarantees about where the block will be called, only that it will have access through the context. In practice, it waits for the concurrency lock and calls the block from the current thread. This is not academic: It means that if you call performBlockAndWait: from the main thread expecting to get to a background thread, you'll still be executing from the main thread.Oscillatory
I
2

You don't have to wait. Your background work executes, then, before it is done, it kicks off work on the main thread, and before it is done, it does your "someOperation." You could replace it with async and it will still work.

Looking at this code, there is no reason to use the blocking versions...

+ (void) myFunction {
    NSManagedObjectContext *backgroundContext = [DatabaseDelegate sharedDelegate].backgroundContext;

    [backgroundContext performBlock:^{
      // Asynchronous... but every command in this block will run before this
      // block returns...
      MyObject *bla = create_my_object_in:backgroundContext;

      [backgroundContext obtainPermanentIDsForObjects:[[backgroundContext insertedObjects] allObjects] error:nil];
      [backgroundContext save:nil];

      [[DatabaseDelegate sharedDelegate].parent.managedObjectContext performBlock:^{
        // Asynchronous, but this whole block will execute...
        [[DatabaseDelegate sharedDelegate].parent updateChangeCount:UIDocumentChangeDone];

        // Do some more stuff
        // This will not run until after the stuff above in this block runs...
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        [queue addOperation:someOperation];
      }];
      // You will reach here BEFORE the code in the previous block executes, but
      // the "someOperation" is in that block, so it will not run until that
      // block is done.
    }];
    // Likewise, you will reach here before the above work is done, but everything
    // will still happen in the right order relative to each other.
   return;
}
Imes answered 19/9, 2012 at 19:42 Comment(3)
I think you might have misunderstood what I want. Your last comment says that "you will reach here before the above work is done, but everything will still happen in the right order relative to each other." What I want is to not reach that return statement before [queue addOperation:someOperation] has executed. That is not guaranteed, is it?Jerri
Why do you need to wait for it? It's going to execute in the right order with respect to everything else. You are not returning any information. There is no reason to wait for it to execute. Anything that must happen after it executes can be done right there in the code. Note that you still can use async callbacks, semaphores, notifications, delegates, NSOperation dependencies... lots of tools, based on your actual goal. From this code... I see no reason to wait for this request to complete before you do other work.Imes
Yes, sorry I should have said that it is important to me because of the code that gets executed after myFunction. In particular, the code that gets executed right after calling myFunction needs to have the guarantee that the newly created MyObject exists in the parent managedObjectContext.Jerri

© 2022 - 2024 — McMap. All rights reserved.