Im trying to figure out right now all my core data is managed on the main thread, with a single context. I used instrument and noticed the save operations are blocking the main thread for quite some time. I would like to run my save operations on a background thread using GCD
Now i understand how to setup this process with creating a new thread / context and listen for "save" notifications to merge back on the main thread.
But when i create a new context all the changes from the main thread's context needs to be inserted / merged into the new, else it wont have anything to save?? i can seem to find any examples for this.
For example on my main context ive:
inserted 2x users
deleted 3 wallposts
updated some friend relationships.
now how do i make my background context know if these changes?
Would it make sense to create the background context right when the application starts and have it listen for NSManagedObjectContextObjectsDidChangeNotification on the main thread to constantly keep it in sync??
In iOS5 you can do this easily with new concurrency types. If you create a context with NSPrivateQueueConcurrencyType, you will not block the main thread. But you have to access the context inside your block passed in to managedObjectContext:performBlock method.
You can't pass NSManagedObject instances between threads. If you are struggling with large datasets you might want to read this post by Marcus Zarra.
Related
I was exploring GCD with core data. I know that managed object context is not thread safe.
I created private context with option "NSPrivateQueueConcurrencyType". As per document I have to use performBlock or performBlockAndWait to do any operation with context, it will do that operation in its own private thread. My questions are,
What are the operation I have to do inside perform block? Is it only accessing the context object or using managed object as well?
Even if I insert a new object to database within "DispatchQueue.global().async"(without using perform block), it works fine. Why? (It is a different thread)
Is that ok doing database operation in different thread, if we create context with "NSPrivateQueueConcurrencyType"?
I can use perform block for every database operation, but our project already has lots of code which are running in private queue. Please help me to understand this better.
This answer is conclusion of answer given by Sandeep Bhandari. Here I assume everyone is clear with Thread and Queue concepts.
Always managed object context should be accessed from single thread at any point of time, because it is not thread safe. If we are using context in multiple thread, we should make sure that it is serialised, so that no two threads are accessing same context at same time. If it does it will crash.
iOS 5 and later, Apple has provided 3 ways to create the context.
confinementConcurrencyType(Deprecated in iOS 10): If you create context with this option, it is your duty to make sure that you are doing all database operation in a thread which created this context.
privateQueueConcurrencyType: If we are using this option to create context, it will create its own internal queue to crate this context and to make sure that all database operation happens in same thread. It will use internal queue only if you perform all your task with this context inside performBlock or performBlockAndWait. If you are using any other queue to do any database operation with this context it will work, if no two thread is accessing at same time. If two threads are accessing it, this will crash. It is very hard to manage this, since that task execution happens at run time depending on resource availability. This type of issues cannot be reproduce that easily. So use those performBlock API to do any database operation with this context.
mainQueueConcurrencyType: If you are using this option to create the context, it will create managed object context in main thread. You need to do any database operation with this context in main thread. You can use performBlock here as well to make sure all task happens in main thread. Even doing any operation with this context will work fine in other thread, if no two thread is accessing at same time.
So I've been playing with this "issue" for a long time here and I keep getting into issues:
Here is more or less what I would like for my dataflow
User Segues to Controller
Controller pings FTP site (if up continue)
Loop through each parent record
Parse each record (this is a slow process) turning it and all sub data into a giant JSON structure which is stored in a temp directory
Update the Screen to the user with the progress of the parsing task
Upload the JSON Structure via FTP
Once successfully uploaded update the "isUploaded" field on the record
Now where I've go into all sort of issues is with keeping the UI updated. I'm designing a somewhat simple UI where we have a status bar that shows the process of each JSON file parsing task. I can make things work but once I' try to get a nice responsive UI i've run into all sorts of issues.
I understand I'm supposed to do CoreData on the main thread, but, in doing so my UI doesn't update and becomes unresponsive.
I segue a NSManagedObjectContext over to this controller and I understand from that objectContext I can likely reference the NSPersistantStoreCoordiantor and create a second NSManagedObjectContext for a second threat and somehow I can likely synchronize these threads.
I've gotten things into a mess with performSelectorOnMainThread and performSelectorInBackground calls all over the place etc.
Either I get things working the way I'd like or I get some sort of error with my managedObjectContext's and I'm thinking perhaps its time for a little rewrite instead of trying to salvage what I have.
Can somebody point me to some ideas about what i should use? I can't seem to exactly wrap my head correctly around the multithreaded core-data concepts as to whether I'm supposed to use NSOperationQueues GCD or some other way of doing things.
CoreData has the ability to operate in a multithreaded environment, as long as you keep managed objects and contexts bound to their proper thread/queue.
To scrape the surface see HERE
You can create a background context by initialising it as a "private queue" bound context (see HERE for more details).
Basically, this would mean that in order to use this context properly you have to execute code on it wrapped in the contexts performBlock: or performBlockAndWait: methods.
you can use these methods to queue "tasks" for the context to execute (serial execution queue).
this code will be executed in the background.
You could also create a context that is confined to a specific thread (NSConfinementConcurrencyType) and then only access it in the thread it was created in without the use of the "perform block" methods.
merging the changes in this context to your main context is done by either setting the main context as a parent for the context after initialisation, or by registering to its "did save" notification and merging the changes to the main context (the first method is easier to implement).
One solution to your flow would be:
Create a main queue context (to be passes around to all view controllers)
Create an operation queue
observe a status entity (fetched in the main context) in your view (directly or by using a FRC)
each operation would create a "private queue" context with the main context as parent
when updates are ready to be committed (including status updates), save the private context and the main context using performBlockAndWait: (just to keep the operation coherent)
This will still affect your main thread if there are many updates as all saves will go through the main context.
Try to segment your saves to avoid locking the main context for too long.
Scenario:
I am on IOS using Magical record configured to operate against a SQLite database. By default, MR configures coredata to serialize all writes back to the parent context on the main thread.
The pattern I use is that when I am not on the main thread I create a separate NSManagedObjectContext for coredata operations using something like MagicalRecord:MR_saveWithBlockAndWait. Magical record creates the context, hooks it up to the parent context, performs whatever operations you specify in the callback block and finally saves. Importantly, the save is supposed to be committed before the operation finishes.
When I am done working on the background thread I usually notify the UI that something has happened; e.g: something is downloaded/uploaded/changed.
On the UI thread I then create a new fetch request using the default context on the main thread. The problem is that occasionally coredata doesn't find the new object I just committed previously. The problem manifests itself in subtle race conditions where if the UI thread is slightly slow due to animations or whatever everything works fine - but sometimes it doesn't find the new object.
From what I have read fetch requests are always supposed to go to the disk. There is also a staleness property on the MOC but it sounds like that is only regarding the cache and is bypassed if you do a fetch request.
Has anyone encountered similar issues and have any insights? thanks.
Sure. If you save changes on a background managed object context, but your UI context has already loaded that object, the UI context may just give you data from its cache instead of from the store file.
The usual approach to using multiple contexts is:
Observe NSManagedObjectContextDidSaveNotification so that you'll know when the background context saves changes.
In your handler for this notification, call mergeChangesFromContextDidSaveNotification: on the UI context, so that it will update itself with changes from the other context.
You probably want to set the mergePolicy on your UI context, because the default is to just give up if there are any conflicting changes.
This applies to any multiple context scenario where each context needs to be updated with changes saved by a different context.
I have seen many times people use many managedObjectContext, but aside from when using the Undo manager, what is the real reason for using multipleManagedObjectContext? Why can it be useful to use more than one? Could you please show a few examples?
Managed object contexts are not thread safe so if you ever need to do any kind of background work with your Coredata objects (i.e. a long running import/export function without blocking the main UI) you will want to do that on a background thread.
In these cases you will need to create a new managed object context on the background thread, iterate through your coredata operation and then notify the main context of your changes.
You can find an example of how this could work here Core Data and threads / Grand Central Dispatch
I'm running into some trouble working with CoreData in a multithreaded app using NSOperations. I am using nested ManagedObjectContexts through MagicalRecord (2.0.3) as follows:
Root Context (saves to disk)
|
Main Thread Context (for populating the UI)
|
Sub-Context(s) (used to add/edit/remove data)
I have a single NSOperationQueue to handle all data processing.
For the most part, things work right, I can asynchronously download data, then feed it to an NSOperation which then writes it to one of the sub-contexts. Saving at the end of the operation pushes the changes to the main context and the UI updates. Great!
The problem is that if a sub-context deletes an entity and saves (pushing it to the main context), a sibling sub-context will still think that it exists. So then if a sibling tries to fault the entity and pull it from it's parent (the main context) I get a crash.
I have 2 questions:
Should I use MOC notifications to merge the changes pushed to the main MOC back to it's other children? I this and was getting another crash...
Should I even have mutltiple sub-contexts? MOC's are supposed to be associated with a single thread (MagicalRecord helps automate this for me), and I have a single NSOperationQueue for saving data, so shouldn't I only have 1 sub-context? I've verified that sometimes my saves are performed by different contexts.
I'd appreciate any advice. Thanks.
You can, and should, have multiple sub contexts. However, I'm not sure if the classic "thread isolation mode" of contexts is the model you should have anymore. That is what you're doing when you say that a context should belong to a particular thread. MagicalRecord 2.0x uses private queue contexts now, and as such will behave a tad differently. There are no rules saying that sibling contexts need to stay in sync. You'd have to do that yourself. A very simple solution would be to listen to "did save" notifications on the context you're saving, and either reset, or create a new context on the other thread. You can do this with notifications, or a completion black that MagicalRecord provides.
Hope this helps