What Is Nsmanagedobjectcontext's Performblock: Used For

What is NSManagedObjectContext's performBlock: used for?

The methods performBlock: and performBlockAndWait: are used to send messages to your NSManagedObjectContext instance if the MOC was initialized using NSPrivateQueueConcurrencyType or NSMainQueueConcurrencyType. If you do anything with one of these context types, such as setting the persistent store or saving changes, you do it in a block.

performBlock: will add the block to the backing queue and schedule it to run on its own thread. The block will return immediately. You might use this for long persist operations to the backing store.

performBlockAndWait: will also add the block to the backing queue and schedule it to run on its own thread. However, the block will not return until the block is finished executing. If you can't move on until you know whether the operation was successful, then this is your choice.

For example:

__block NSError *error = nil;
[context performBlockAndWait:^{
myManagedData.field = @"Hello";
[context save:&error];
}];

if (error) {
// handle the error.
}

Note that because I did a performBlockAndWait:, I can access the error outside the block. performBlock: would require a different approach.

From the iOS 5 core data release notes:

NSManagedObjectContext now provides structured support for concurrent operations. When you create a managed object context using initWithConcurrencyType:, you have three options for its thread (queue) association

  • Confinement (NSConfinementConcurrencyType).

    This is the default. You promise that context will not be used by any thread other than the one on which you created it. (This is exactly the same threading requirement that you've used in previous releases.)

  • Private queue (NSPrivateQueueConcurrencyType).

    The context creates and manages a private queue. Instead of you creating and managing a thread or queue with which a context is associated, here the context owns the queue and manages all the details for you (provided that you use the block-based methods as described below).

  • Main queue (NSMainQueueConcurrencyType).

    The context is associated with the main queue, and as such is tied into the application’s event loop, but it is otherwise similar to a private queue-based context. You use this queue type for contexts linked to controllers and UI objects that are required to be used only on the main thread.

When should I use performBlock method?

The methods performBlock: and performBlockAndWait: are used to send messages to your NSManagedObjectContext whether the MOC was initialized using NSPrivateQueueConcurrencyType or NSMainQueueConcurrencyType. If you do anything with one of these context types, you do it in a block.

performBlock: will add the block to the backing queue and schedule it to run on its own thread. You might use this for long persist operations as this block will return immediately

performBlockAndWait: will also add the block to the backing queue and schedule it to run on its own thread. However, the block will not return until the block is finished executing, hence, it is a blocking call.

Can only use -performBlock: on an NSManagedObjectContext that was created with a queue.' Error

Create your ManagedObjectContext like this

NSManagedObjectContext *moc = [[NSManagedObjectContext alloc]
initWithConcurrencyType:NSMainQueueConcurrencyType];

NSMainQueueConcurrencyType creates a context that is associated with the main dispatch queue and thus the main thread. You could use such a context to link it to objects that are required to run on the main thread, for example UI elements.

NSPrivateQueueConcurrencyType creates and manages a private dispatch queue to operate on. You must use the new methods performBlock: or performBlockAndWait:. The context will then execute the passed blocks on its own private queue.

Finally, NSConfinementConcurrencyType is the default type and can be used only within the thread where it has been created.

Can only use -performBlock: on an NSManagedObjectContext that was created with a queue

performBlock can only be used with a managed object context (MOC) of the NSMainQueueConcurrencyType or
NSPrivateQueueConcurrencyType. In your case, you should create the context with

NSManagedObjectContext *context = [[NSManagedObjectContext alloc]
initWithConcurrencyType:NSPrivateQueueConcurrencyType];

And there is no need to create a dispatch queue or use dispatch_async().
The MOC creates and manages its own queues,
and performBlock ensures that the block is executed on the MOC's queue.

Can you use an NSManagedObject outside of it's context's performBlock?

NSManagedObject is not supposed to be used outside of its managedObjectContexts thread/queue (sometime it works and some times you crash ==> don't do it).

CoreData does not guarantee safe read access to the object.

To access an object owned by a "private queue" context, always use either [context performBlock:...] or [context performBlockAndWait:...], unless you access its objectID or managedObjectContext properties.

NSManagedObjectContext performBlockAndWait: doesn't execute on background thread?

Tossing in another answer, to try an explain why performBlockAndWait will always run in the calling thread.

performBlock is completely asynchronous. It will always enqueue the block onto the queue of the receiving MOC, and then return immediately. Thus,

[moc performBlock:^{
// Foo
}];
[moc performBlock:^{
// Bar
}];

will place two blocks on the queue for moc. They will always execute asynchronously. Some unknown thread will pull blocks off of the queue and execute them. In addition, those blocks are wrapped within their own autorelease pool, and also they will represent a complete Core Data user event (processPendingChanges).

performBlockAndWait does NOT use the internal queue. It is a synchronous operation that executes in the context of the calling thread. Of course, it will wait until the current operations on the queue have been executed, and then that block will execute in the calling thread. This is documented (and reasserted in several WWDC presentations).

Furthermore, performBockAndWait is re-entrant, so nested calls all happen right in that calling thread.

The Core Data engineers have been very clear that the actual thread in which a queue-based MOC operation runs is not important. It's the synchronization by using the performBlock* API that's key.

So, consider 'performBlock' as "This block is being placed on a queue, to be executed at some undetermined time, in some undetermined thread. The function will return to the caller as soon as it has been enqueued"

performBlockAndWait is "This block will be executed at some undetermined time, in this exact same thread. The function will return after this code has completely executed (which will occur after the current queue associated with this MOC has drained)."

EDIT

Are you sure of "performBlockAndWait does NOT use the internal queue"?
I think it does. The only difference is that performBlockAndWait will
wait until the block's completion. And what do you mean by calling
thread? In my understanding, [moc performBlockAndWait] and [moc
performBloc] both run on its private queue (background or main). The
important concept here is moc owns the queue, not the other way
around. Please correct me if I am wrong. – Philip007

It is unfortunate that I phrased the answer as I did, because, taken by itself, it is incorrect. However, in the context of the original question it is correct. Specifically, when calling performBlockAndWait on a private queue, the block will execute on the thread that called the function - it will not be put on the queue and executed on the "private thread."

Now, before I even get into the details, I want to stress that depending on internal workings of libraries is very dangerous. All you should really care about is that you can never expect a specific thread to execute a block, except anything tied to the main thread. Thus, expecting a performBlockAndWait to not execute on the main thread is not advised because it will execute on the thread that called it.

performBlockAndWait uses GCD, but it also has its own layer (e.g., to prevent deadlocks). If you look at the GCD code (which is open source), you can see how synchronous calls work - and in general they synchronize with the queue and invoke the block on the thread that called the function - unless the queue is the main queue or a global queue. Also, in the WWDC talks, the Core Data engineers stress the point that performBlockAndWait will run in the calling thread.

So, when I say it does not use the internal queue, that does not mean it does not use the data structures at all. It must synchronize the call with the blocks already on the queue, and those submitted in other threads and other asynchronous calls. However, when calling performBlockAndWait it does not put the block on the queue... instead it synchronizes access and runs the submitted block on the thread that called the function.

Now, SO is not a good forum for this, because it's a bit more complex than that, especially w.r.t the main queue, and GCD global queues - but the latter is not important for Core Data.

The main point is that when you call any performBlock* or GCD function, you should not expect it to run on any particular thread (except something tied to the main thread) because queues are not threads, and only the main queue will run blocks on a specific thread.

When calling the core data performBlockAndWait the block will execute in the calling thread (but will be appropriately synchronized with everything submitted to the queue).

I hope that makes sense, though it probably just caused more confusion.

EDIT

Furthermore, you can see the unspoken implications of this, in that the way in which performBlockAndWait provides re-entrant support breaks the FIFO ordering of blocks. As an example...

[context performBlockAndWait:^{
NSLog(@"One");
[context performBlock:^{
NSLog(@"Two");
}];
[context performBlockAndWait:^{
NSLog(@"Three");
}];
}];

Note that strict adherence to the FIFO guarantee of the queue would mean that the nested performBlockAndWait ("Three") would run after the asynchronous block ("Two") since it was submitted after the async block was submitted. However, that is not what happens, as it would be impossible... for the same reason a deadlock ensues with nested dispatch_sync calls. Just something to be aware of if using the synchronous version.

In general, avoid sync versions whenever possible because dispatch_sync can cause a deadlock, and any re-entrant version, like performBlockAndWait will have to make some "bad" decision to support it... like having sync versions "jump" the queue.

CoreData: Can only use -performBlock: on an NSManagedObjectContext that was created with a queue

Example taken from an actual published app, you should make use of 2+ MOCs.

  1. One for the main thread
  2. One for each background thread.

This allows for background threads to safely perform MOC operations, without running the risk of conflicting with each other. Notice how the secondary MOC(s) use the -setParentContext: to refer to the main MOC.

This backgroundMOC, when created and used from a background thread, is safe and won't throw the error.

When you are done with your modifications, perform a save on the background thread:

[backgroundMOC performBlockAndWait:^{
if([backgroundMOC hasChanges]) {
NSError * error;
[backgroundMOC save:&error];
// handle error
}
}];

To create both MOCs used in the example above, you could use the code below (ObjC)

For main thread, use:

@property(strong) NSManagedObjectContext * 

- (NSManagedObjectContext *)mainMOC mainManagedObjectContext;
{
NSThread * currentThread = [NSThread currentThread];
NSAssert([currentThread isMainThread], @"managedObjectContext invoked from %@",currentThread);
if([currentThread isMainThread]) {
if(!_mainManagedObjectContext) {
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];

if(coordinator) {
_mainManagedObjectContext = [[NSManagedObjectContext alloc]
initWithConcurrencyType:NSMainQueueConcurrencyType];
[_mainManagedObjectContext setPersistentStoreCoordinator: coordinator];
[_mainManagedObjectContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
}
}

return _mainManagedObjectContext;
}
return nil;
}

For background thread, use:

- (NSManagedObjectContext *)threadMOCWithMainMOC:(NSManagedObjectContext *)mainMOC
{
NSManagedObjectContext * threadManagedObjectContext = [[NSManagedObjectContext alloc]
initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[threadManagedObjectContext setParentContext:[self.db mainManagedObjectContext]];
[threadManagedObjectContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
return threadManagedObjectContext;
}

Nested performBlock: on NSManagedObjectContext

Yes, performBlockAndWait is reentrant. Directly from Apple's release notes...

Core Data formalizes the concurrency model for the
NSManagedObjectContext class with new options. When you create a
context, you can specify the concurrency pattern to use with it:
thread confinement, a private dispatch queue, or the main dispatch
queue. The NSConfinementConcurrencyType option provides the same
behavior that was present on versions of iOS prior to 5.0 and is the
default. When sending messages to a context created with a queue
association, you must use the performBlock: or performBlockAndWait:
method if your code is not already executing on that queue (for the
main queue type) or within the scope of a performBlock... invocation
(for the private queue type). Within the blocks passed to those
methods, you can use the methods of NSManagedObjectContext freely. The
performBlockAndWait: method supports API reentrancy. The performBlock:
method includes an autorelease pool and calls the
processPendingChanges method upon completion.



Related Topics



Leave a reply



Submit