I have an application with Core Data where I frequently use:
[[MyCoreDataStore defaultStore].mainQueueContext performBlockAndWait:^{
// perform some operations
[MyCoreDataStore saveMainQueueContext];
}];
During an external code audit it was pointed out that performBlockAndWait: on the main context was wrong.
I was reading some questions from StackOverflow but could not find the canonical way to handle concurrency and not block the main context
Core Data main context concurrency
NSManagedObjectContext performBlockAndWait: doesn't execute on background thread?
performBlockAndWait On Child Context with Private Queue Deadlocks Parent on iOS 7
Multiple contexts in the main thread: why and when use them?
So what's the canonical way to deal with concurrence and keep the main context unblocked?
Related
I am having a structure of parent/child relationship context.
The parent is main context and the child is private concurrent context.
when the child does some changes and do save. The main context (object) receives the notification and do NSManagedObjectContextDidSave.
The problem is that I am wondering if this action is thread safe? because even it's wrapped in is't own context/thread (inside mainContext.performBlock), the other thread - child concurrent thread for example, can do the fetch. Will it corrupt the data when these 2 actions happen at exact time?
performBlock : synchronously performs the block on the context's queue. and is not thread safe in case of saving multiple records.
performBlockAndWait: synchronously performs the block on the context's queue. May safely be called subroutine.
So I've been reading about and working with Core Data pretty extensively over the past few weeks, and I've ended up implementing my Core Data stack using the following setup:
Save context - root context tied to the store, on PrivateQueue
Main context - running on main thread, child of save context
Edit contexts - created on-demand in any thread to edit UI data in the background as children of main context
With this setup, I haven't been having any issues, and I've taken the necessary precautions to prevent multiple background edit contexts from calling [editMoc save] simultaneously, but I'm not seeing how this is safe relative to the main thread/context. Specifically, all the code I keep seeing while using this setup is like the following:
[edit performBlock^{
[edit save:nil];
[main performBlock:^{
[main save:nil];
[root performBlock...]
}];
}];
Now the thing I have not been able to figure out is this: every time the child context saves, the data is being propagated to the main context, but nothing is happening to ensure that the data isn't currently being read from/acted upon in the main context, right? Essentially, I feel like something like this would be more appropriate:
[edit performBlock^{
dispatch_async(dispatch_get_main_queue(), ^{
[edit save:nil];
});
[main performBlock:^{
[main save:nil];
[root performBlock....]
}];
}];
At least if it were done that way, we'd ensure that the propagation is safe relative to the main thread/UI, no? This sort of ties in with my other question of "why should the main context really be associated with the main thread anyways?" Right now if I need to execute a fetch, I do it from the main context and I can understand that it then makes sense for it to be on the main thread, but if propagations can just happen "whenever" (since we aren't doing them on the main thread) what is the ultimate point of having it tied to the main thread? I don't even need to do a fetch because the data has seemingly unsafely arrived in the main context from the child save, yeah? From there a simple [tableView reload] suffices.
Any help is appreciated!
EDIT:
So I think I've found the answer to my question, which is that Core Data does in fact internally push the changes from the child context to the parent context using the correct thread (i.e. using the parent context's queue - which in this case would be the main queue). Meaning that it would in fact have to wait for anything else happening on the main thread that may have been using that data before it will dirty the main context.
When I send a performBlock message to my MOC of type NSPrivateQueueConcurrencyType, like this:
[self.privateManagedObjectContext performBlockAndWait:^{
if ([[NSThread currentThread] isMainThread]) {
NSLog(#"executing on the main thread!!");
}
…
}];
I find that, by default, this executes on the main thread. The conditional in the above code triggers, and the Issue Navigator indicates that execution is occurring on Thread 1 in the NSManagedObject Queue.
This is very puzzling to me, because Apple tells us that "each thread must have its own entirely private managed object context." Given that an MOC of type NSMainQueueConcurrencyType will use the main thread, doesn't it violate thread confinement for an MOC of type NSPrivateQueueConcurrencyType to use the main thread?
Is the execution of my code on the main thread normal? Have I misunderstood thread confinement? I understand that a queue is not necessarily tied to a particular thread, but in this case it seems the private MOC queue should at a minimum avoid the main thread, if not have a single go-to thread. I'm having some weird bugs, so I need to figure out what's going on. Thanks!
This optimization is possible because performBlockAndWait: executes the block
synchronously, i.e. the method does not return until the block has finished.
Therefore the block will not be executed in parallel with other operations on
the main thread.
(For the same reason, dispatch_sync(queue, ...) may execute a block on the main thread
instead of a separate thread.)
Utility.managedObjectContext().performBlockAndWait({
})
dispatch_sync(dispatch_get_main_queue(), {
})
Curious what is the difference between the two code above? context was created with .MainQueueConcurrencyType option.
If I perform blocks on the main queue, are queues executed in a FIFO order? Or can they overlap, operation mingle? I.e. (a1,a2,a3),(b1,b2,b3) can result (a1,b1,a2,a3,b2,b3)?
You are mixing two entirely different concepts here, but since it is the main thread/context/queue, your mix is masked and it "works".
Managed object context's performBlockAndWait: and performBlock: methods do not make any guarantees on which thread the block is executed, only that data accessed/mutated is safely accessed. Since your context is of main queue concurrency type, it is the exception in that it is safe to touch its objects outside of the performBlockAndWait: and performBlock: methods, on the main thread only. So when you queue your block to run on the main queue, it is guaranteed to run on the main thread, and thus your data is safe.
Block execution on the main thread is not atomic. Otherwise, what is the point of multithreading? To ensure data safety, you must performBlockAndWait: and performBlock: methods are called when accessing data. You are guaranteed that main queue scheduled blocks will run uninterrupted by other main queue scheduled blocks, and managed object context queues (background or main) are serial, so only one block will be allowed to concurrently access data.
In iOS 5, NSManagedObjectContext has a couple of new methods, performBlock: and performBlockAndWait:. What are these methods actually used for? What do they replace in older versions? What kind of blocks are supposed to be passed to them? How do I decide which to use? If anyone has some examples of their use it would be great.
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.
They allow you to access the same managedObjectContext accross threads.
I am not really sure I am correct, but this is how I use it.
You use performBlockAndWait is like "usual". You do not need it if you execute the managedObjectContext only on one thread. If you execute it on many threads then yes you will need performBlock.
So, if you're on main thread, you do not need to do performBlockAndWait for the main managedObjectContext. At least I don't and is doing fine.
However if you access that managedObjectContext on other threads then yes you will need to do performBlockAndWait.
So that's the purpose of performBlock and performBlockAndWait.
Would someone please correct me if I am wrong here. Of course if you access the context only on one thread then you can simply use the default.