Behavior differences between performBlock: and performBlockAndWait:?

AppsDev picture AppsDev · Aug 25, 2015 · Viewed 10.6k times · Source

I'm creating an NSManagedObjectContext in a private queue to handle data updates I take from files and/or services:

NSManagedObjectContext *privateContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
privateContext.persistentStoreCoordinator = appDelegate.persistentStoreCoordinator;

Since I'm using a private queue, I don't fully understand the difference between performBlock: and performBlockAndWait: methods... To perform my data updates I'm currently doing this:

[privateContext performBlock: ^{

        // Parse files and/or call services and parse
        // their responses

        // Save context
        [privateContext save:nil];

        dispatch_async(dispatch_get_main_queue(), ^{
            // Notify update to user
        });
    }];

In this case, my data updates are made synchronoulsy and sequentially, so I suppose that is the correct place to save the context, right? If I'm doing something wrong, I'd appreciate if you let me know. On the other hand, would this code be equivalent?:

[privateContext performBlockAndWait: ^{

        // Parse files and/or call services and parse
        // their responses

        // Save context
        [privateContext save:nil];
    }];

// Notify update to user

Again I guess that is the correct place to save the context... what are the differences between both methods (if any, in this case)?

What if instead of performing synchronous service calls or files parsing, I need to perform asynchronous service calls? How would these data updates be managed?

Thanks in advance

Answer

Jody Hagins picture Jody Hagins · Aug 25, 2015

You are correct in that anything you want to do with a MOC must be done within either performBlock or performBlockAndWait. Note that retain/release is thread safe for managed objects, so you don't have to be inside one of those blocks to retain/release reference counts on managed objects.

They both utilize a synchronous queue to process messages, which means that only one block will execute at a time. Well, that's almost true. See the descriptions of performBlockAndWait. In any event, the access to the MOC will be serialized such that only one thread is accessing the MOC at a time.

tl;dr Don't worry about the difference, and always use performBlock.

Factual Differences

There are a number of differences. I'm sure there are more, but here are the ones that I think are most important to understand.

Synchronous vs. Asynchronous

performBlock is asynchronous, in that it returns immediately, and the block is executed at some time in the future, on some undisclosed thread. All blocks given to the MOC via performBlock will execute in the order they were added.

performBlockAndWait is synchronous, in that the calling thread will wait until the block has executed before returning. Whether the block runs in some other thread, or runs in the calling thread is not all that important, and is an implementation detail that can't be trusted.

Note, however, that it could be implemented as "Hey, some other thread, go run this block. I'm gonna sit here doing nothing until you tell me it's done." Or, it could be implemented as "Hey, Core Data, give me a lock that prevents all those other blocks from running so I can run this block on my own thread." Or it could be implemented in some other way. Again, implementation detail, which could change at any time.

I'll tell you this though, the last time I tested it, performBlockAndWait executed the block on the calling thread (meaning the second option in the above paragraph). This is only really information to help you understand what is going on, and should not be relied upon in any way.

Reentrancy

performBlock is always asynchronous, and is thus not reentrant. Well, some may consider it reentrant, in that you can call it from within a block that was called with performBlock. However, if you do this, all calls to performBlock will return immediately, and the block will not execute until at least the currently executing block completely finishes its work.

[moc performBlock:^{
    doSomething();
    [moc performBlock:^{
      doSomethingElse();
    }];
    doSomeMore();
}];

These functions will always be executed in this order:

doSomething()
doSomeMore()
doSomethingElse()

performBlockAndWait is always synchronous. Furthermore, it is also reentrant. Multiple calls will not deadlock. Thus, if you end up calling performBlockAndWait while you are inside a block that was being run as a result of another performBlockAndWait, then it's OK. You will get the expected behavior, in that the second call (and any subsequent calls) will not cause a deadlock. Furthermore, the second one will completely execute before it returns, as you would expect.

[moc performBlockAndWait:^{
    doSomething();
    [moc performBlockAndWait:^{
      doSomethingElse();
    }];
    doSomeMore();
}];

These functions will always be executed in this order:

doSomething()
doSomethingElse()
doSomeMore()

FIFO

FIFO stands for "First In First Out" which means that blocks will be executed in the order in which they were put into the internal queue.

performBlock always honors the FIFO structure of the internal queue. Every block will be inserted into the queue, and only run when it is removed, in FIFO order.

By definition, performBlockAndWait breaks FIFO ordering because it jumps the queue of blocks that have already been enqueued.

Blocks submitted with performBlockAndWait do not have to wait for other blocks that are running in the queue. There are a number of ways to see this. One simple one is this.

[moc performBlock:^{
    doSomething();
    [moc performBlock:^{
      doSomethingElse();
    }];
    doSomeMore();
    [moc performBlockAndWait:^{
      doSomethingAfterDoSomethingElse();
    }];
    doTheLastThing();
}];

These functions will always be executed in this order:

doSomething()
doSomeMore()
doSomethingAfterDoSomethingElse()
doTheLastThing()
doSomethingElse()

It's obvious in this example, which is why I used it. Consider, however, if your MOC is getting stuff called on it from multiple places. Could be a bit confusing.

The point to remember though, is that performBlockAndWait is preemptive and can jump the FIFO queue.

Deadlock

You will never get a deadlock calling performBlock. If you do something stupid inside the block, then you could deadlock, but calling performBlock will never deadlock. You can call it from anywhere, and it will simply add the block to the queue, and execute it some time in the future.

You can easily get deadlocks calling performBlockAndWait, especially if you call it from a method that an external entity can call or indiscriminately within nested contexts. Specifically, you are almost guaranteed to deadlock your applications if a parent calls performBlockAndWait on a child.

User Events

Core Data considers a "user event" to be anything between calls to processPendingChanges. You can read the details of why this method is important, but what happens in a "user event" has implications on notifications, undo management, delete propagation, change coalescing, etc.

performBlock encapsulates a "user event" which means the block of code is automatically executed between distinct calls to processPendingChanges.

performBlockAndWait does not encapsulate a "user event." If you want the block to be treated as a distinct user event, you must do it yourself.

Auto Release Pool

performBlock wraps the block in its own autoreleasepool.

performBlockAdWait does not provide a unique autoreleasepool. If you need one, you must provide it yourself.

Personal Opinions

Personally, I do not believe there are very many good reasons to use performBlockAndWait. I'm sure someone has a use case that can't be accomplished in any other way, but I've yet to see it. If anyone knows of that use case, please share it with me.

The closest is calling performBlockAndWait on a parent context (don't ever do this on a NSMainConcurrencyType MOC because it could lock up your UI). For example, if you want to ensure that the database has completely saved to disk before the current block returns and other blocks get a chance to run.

Thus, a while ago, I decided to treat Core Data as a completely asynchronous API. As a result, I have a whole lot of core data code, and I do not have one single call to performBlockAndWait, outside of tests.

Life is much better this way. I have much fewer problems than I did back when I thought "it must be useful or they wouldn't have provided it."

Now, I simply no longer have any need for performBlockAndWait. As a result, maybe it has changed some, and I just missed it because it no longer interests me... but I doubt that.