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!
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.
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With