Concurrency with core data - ios

I am using multi-threading to get data, parse it, create objects and store them. And after this is all done, I want the window to be shown.
But now I have 2 issues:
I have a deadlock
My barrier does not act as a barrier.
I think the deadlock is because I am updating the managedObjectContext in several threads at once.
So I changed my managedObjectContext with the ConcurrencyType:
__managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
And created an importContext for the concurrency queue and assigned the parentContext:
NSManagedObjectContext *importContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
importContext.parentContext = self.managedObjectContext;
And put my operations in a performBlock for the importContext:
[importContext performBlock:^{
dispatch_async(backgroundQueue, ^{
[myObject methodAWithContext:importContext];
});
dispatch_async(backgroundQueue, ^{
[myObject methodBWithContext:importContext];
});
dispatch_async(backgroundQueue, ^{
[myObject methodCWithContext:importContext];
});
dispatch_barrier_async(backgroundQueueM, ^{
// create barrier to wait for the first 3 threads to be completed.
dispatch_async(dispatch_get_main_queue(), ^{
// Save the data from the importContext tot the main context on the main queue
NSError *importError = nil;
[importContext save:&importError];
[importContext.parentContext performBlock:^{
NSError *parentError = nil;
[importContext.parentContext save:&parentError];
}];
[self.window makeKeyAndVisible];
});
});
}];
Approach 1:
In each method, I select a subset of object, delete these and then create new objects and save this.
(I thought the delete was quicker than doing a fetch and check for the existence for every object to be created).
So:
In Method A I select all AObjects, delete them and create new AObjects.
In Method B I select all BObjects, delete them and create new BObjects.
In Method C I select all CObjects, delete them and create new CObjects.
But then I get an error "An NSManagedObjectContext cannot delete objects in other contexts".
So approach 2:
I removed the delete. But now I get various different errors.....
And the barrier does not wait for the other threads to be executed.
Q1: What am I doing wrong?
Q2: how do I get the barrier to wait for the 3 threads to be completed
Q3: how can I delete / purge objects on various threads?
(I have read the Apple release notes and doc's, but I can't find this a clear explanation on the combination for multithreading and managedContext.)

You cannot call dispatch_async within performBlock. A managed object context of type NSPrivateQueueConcurrencyType has it's own dispatch queue for executing the operations.
You try to do several operations in parallel by moving them to a different dispatch queue, but that is not possible.
If you really have to do multiple operations in parallel, you must create a private concurrency type MOC for each operation.
ADDED:
There are several ways to wait for all operations to complete:
You could increment a counter at the end of each performBlock: and check if it's value is (in your example) 3.
You could create a semaphore (dispatch_semaphore_create) for each operation with initial value zero, wait for all the semaphores (dispatch_semaphore_wait) and signal the semaphore at the end of each performBlock.
And I am sure that there are better/more elegant/more sophisticated ways to do this.
BUT: As I re-read your question, I see that you try to delay the
[self.window makeKeyAndVisible];
until all Core Data fetch operations have completed. This is not a good design, because the user will see nothing until your data import is done.
A better design is to show an initial view immediately, and update that view when the background operations have fetched data.

Related

Core Data concurrency issue and how to fix

I use multiple contexts in my Core Data app, and have recently had some core data concurrency crashes. I have added -com.apple.CoreData.ConcurrencyDebug 1 to help track these down, but I am not understanding how to fix the issue that is shown.
Here is what I am doing:
- (void)getEvents:(void (^)(NSArray *fetchedItems))completionBlock {
// Initialize Fetch Request
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"ZSSCDEvent"];
// Initialize Asynchronous Fetch Request
NSAsynchronousFetchRequest *asynchronousFetchRequest = [[NSAsynchronousFetchRequest alloc] initWithFetchRequest:request completionBlock:^(NSAsynchronousFetchResult *result) {
dispatch_async(dispatch_get_main_queue(), ^{
// Dismiss Progress HUD
[SVProgressHUD dismiss];
// Process Asynchronous Fetch Result
if (result.finalResult) {
NSArray *results = result.finalResult;
completionBlock(results);
// Reload Table View
[self.activityIndicator stopAnimating];
[self.tableViewList reloadData];
}
});
}];
// Execute Asynchronous Fetch Request
[self.managedObjectContext performBlock:^{
// Execute Asynchronous Fetch Request
NSError *asynchronousFetchRequestError = nil;
NSAsynchronousFetchResult *asynchronousFetchResult = (NSAsynchronousFetchResult *)[self.managedObjectContext executeRequest:asynchronousFetchRequest error:&asynchronousFetchRequestError];
if (asynchronousFetchRequestError) {
NSLog(#"Unable to execute asynchronous fetch result.");
NSLog(#"%#, %#", asynchronousFetchRequestError, asynchronousFetchRequestError.localizedDescription);
}
}];
}
This gives me a Enqueued from com.apple.main-thread (Thread 1) error. This is where I am confused, since I am running this on the main thread and didn't think I needed to use my private context here.
Any ideas on why I am getting a concurrency issue here?
EDIT: It looks like someone else had the exact issue and thinks it is an Xcode bug: CoreData asynchronous fetch causes concurrency debugger error
Every managedObject has a context. Each context has one and only one thread that it can run on. ManagedObjects are NOT thread safe - not even for reading. Passing managedObjects around with completions blocks is a bad practice. It can be hard to figure out which managedObjects are supposed to be on which thread. Also, even if you are passing it around on the correct thread it is still a bad practice. When you do a dispatch_async the entity may have deleted from the database in the interim and accessing the managedObject will cause a crash. A good practice is that any method that does a fetch should be explicitly told which context to use and return synchronously. In your code the method is using self.managedObjectContext, but I have no way to know looking at you code what thread that is related to. Furthermore the pointer to the context may change and that can cause bugs that are very hard to track down.
NSAsynchronousFetchResult contains managedObjects, so is not thread safe and can only be used inside that completion block (which is running on the correct thread for the objects). You cannot pass them to another thread. If you return them in a block then that code must also not pass them to another thread. You should just do whatever you need to do with them inside the block and then discard them.
If you need to display the information to the user, then generally it is better to just do the fetch on the main thread synchronously and get managedObjects that are associated with a main thread context. If your fetch is taking to long then you should fix that - there is no reason for a fetch to take so long. In your case you appear to be fetching ALL the items in you database. That is a mistake. You should use a predicate to only get the ones that you need.
Another possibility is to copy the values of managedObjects into a thread safe object (a dictionary or a NSObject subclass).
TL;DR You probably don't need a NSAsynchronousFetchRequest. Use a regular fetch request on the main thread and return synchronously. Use a predicate to limit the results only to the objects you are actually displaying.

Core Data: Parent context and change propagation

I have the following Core Data setup in my app:
Persistent Store Coordinator
^ Background MOC (NSPrivateQueueConcurrencyType)
^ Main Queue MOC (NSMainQueueConcurrencyType)
Here is the initialization code:
_backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_backgroundContext setPersistentStoreCoordinator:self.coordinator];
_mainContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_mainContext setParentContext:_backgroundContext];
I use the background MOC for importing large amounts of data. I also use it to perform complex fetch requests in the background and then pass the object IDs to the main queue to fetch the objects using these IDs.
This works quite well. However, I am not sure how to let the main queue MOC know about the changes made in the background MOC. I know that if I perform a fetch request on the main queue MOC, it will get the changes, but that's not what I want.
Is it OK to use the NSManagedObjectContextObjectsDidChangeNotification notification posted by the background MOC and call mergeChangesFromContextDidSaveNotification: on the main queue MOC? This should then cause the NSManagedObjectContextObjectsDidChangeNotification notification of the main queue MOC to fire. I am listening for this notification in my view controllers and examine the userInfo for changes and redisplay data accordingly.
I think you usually do it this way if you have one persistent store coordinator with two attached MOCs. But I am not sure if it is the right way to do, when you have child/parent contexts.
Having the main MOC use a private parent MOC for asynchronous I/O is fine. However, you should not use that parent MOC for anything but performing background work on behalf of the main MOC. There are many reasons for this (among them performance and nasty issues related to transient object IDs).
If you want to do background updating of the store, here is what I suggest.
PSC <--+-- PrivateMOC <---- MainMOC
|
+-- BackgroundPrivateMOC
This will allow background operation that causes the least interruption to the main MOC, while allowing the PSC caches to be shared.
Now, for sharing data...
The MainMOC should listen for and merge DidSave notifications from the BackgroundPrivateMO.
The BackgroundMOC can listen for and merge DidSave notifications from the PrivateMOC.
This allows merging to use only permanent object IDs and optimizes performance.
I'd say that listening to NSManagedObjectContextObjectsDidChangeNotification notification is not probably the best solution.
The way I do it and it works is following.
Here is main context creation:
_mainContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_mainContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
_mainContext.persistentStoreCoordinator = _persistentStoreCoordinator;
Here is background context creation:
_backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
_backgroundContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
_backgroundContext.parentContext = self.mainContext;
Now, background context is only for writing (or reading) objects (may be in background thread). Main context is only for reading from the main queue.
Save on background context should look like:
__block BOOL saved = [_backgroundContext save:error];
if (saved && _backgroundContext.parentContext) {
[_backgroundContext.parentContext performBlockAndWait:^{
saved = [self.parentContext save:error];
}];
}
This save method guarantees that all changes will be propagated to main context. If you do a lot of work in many background threads get more familiar with performBlockAndWait: method, which provides mutual exclusion on context.
If you want to be notified about objects' changes, you don't have to listen for notification, you can simply setup NSFetchedResultsController and register as its delegate.

Storing thread context specific data in a Core Data NSPrivateQueueConcurrencyType thread

I need to store thread context specific data in a block executing with a NSPrivateQueueConcurrencyType thread. Here is an example of what I would like to do within the block (a simplified example of the context data I will be storing is given but you get the point):
dispatch_queue_t myQueue = dispatch_get_current_queue();
NSMutableDictionary *myContext = #{#"Context": #"ContextData"};
dispatch_set_context(myQueue, (__bridge_retained void *)myContext);
But the problem is the function dispatch_get_current_queue() used to get a reference to the current queue is deprecated, so I don't want to use it. I can't see any other mechanism for retrieving a reference to this queue so I can saving context data against it. I'm now wondering if Apple have deprecated dispatch_get_current_queue() because they don't want people doing what I am trying to do - though I can't see any documentation warning about this. 1. Do you know of any reasons this is a bad thing to do? 2. Do you know of any other way to save context data relative to the current thread where the code used should also be able to work if the thread happens to be the system provided CoreDataPrivateQueue type?
OK, so the NSThread equivalent calls can be made instead. On a single system generated MOC of type NSPrivateQueueConcurrencyType this works. Will update if I encounter any problems relating to thread lifetime once I have worked with it for a bit:
[moc performBlockAndWait:^{
NSThread *currentThread = [NSThread currentThread];
NSMutableDictionary *myThreadDict = currentThread.threadDictionary;
[myThreadDict setObject:#"Some thread data" forKey:#"IRThreadData"];
}];
[moc performBlockAndWait:^{
NSThread *currentThread = [NSThread currentThread];
NSMutableDictionary *myThreadDict = currentThread.threadDictionary;
NSLog(#"Retreived dictionary value = %#", [myThreadDict objectForKey:#"IRThreadData"]);
}];

MagicalRecord : how to perform background saves

I am building a news application, which basically fetches data from a distant server, using AFNetworkOperation (all operations are put in a NSOperationQueue in order to properly manage the synchronisation process and progress).
Each completion block of each AFNetworkOperation creates/deletes/updates core data entities.
At the whole end of the synchronisation process, in order to make all changes persistent, I perform a full save with following lines of code
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(queue, ^{
NSLog(#"saveInBackground : starting...");
[[NSManagedObjectContext defaultContext] saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
NSLog(#"saveInBackground : finished!");
}];
});
Unfortunately it always blocks the main thread during my save operation.
I might not use MagicalRecord properly and so any advice would be welcome.
After digging deeper inside MagicalRecord, it seems that my code is working well and does not block main thread at all.
My issue is not on MagicalRecord, but on the way I should use it on completion blocks of afnetworking operation.
I Will start a new discussion to provide full details on it.

Multithreaded Core Data - NSManagedObject invalidated

As the title suggests im working with a Core Data Application which gets filled with objects in different background threads (XML Parsing)
In my background thread I'm doing this
managedContext = [[NSManagedObjectContext alloc] init];
[managedContext setUndoManager:nil];
[managedContext setPersistentStoreCoordinator: [[DataManager sharedManager] persistentStoreCoordinator]];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:managedContext];
NSMutableArray *returnSource = [[self parseDocument:doc] retain];
[managedContext save:&error];
if (error) {
NSLog(#"saving error in datafeed");
}
[managedContext reset];
[self performSelectorOnMainThread:#selector(parseCompleteWithSource:) withObject:returnSource waitUntilDone:YES];
The Merge method looks like this:
NSManagedObjectContext *mainContext = [[DataManager sharedManager] managedObjectContext];
// Merge changes into the main context on the main thread
[mainContext performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:)
withObject:notification
waitUntilDone:YES];
[[NSNotificationCenter defaultCenter] removeObserver:self];
I think the merge is successful but as i want to display it in an UITableView it always tells me that my objects are invalidated which is to be expected because of
[managedContext reset];
What i want to do is show the Items which are currently in the database, in the background parse the xml and if thats finished i want to update the UITableView with the new / updated objects. How would I do that, can i "update" the objects to the other Context somehow or is the merge not working correctly?
Do I need to define something specific in the Main ObjectContext?
I've tried different mergepolicies without any luck.
Hope you can help me, thanks!
I believe your problem is the contents of the returnSource array. If that is a bunch of NSManagedObject instances then they will have been created on the background thread by the background thread context.
You call to -[NSManagedObjectContext reset] will invalidate them, since that is what you explicitly tell the context to do. But that is not the big problem.
You then go on to send the array to a the main thread, passing NSManagedObjectinstances over thread borders, and between contexts is a big no-no.
What you need to do is:
Create an array with the NSManagedObjectIDs of the NSManagedObject.
Send the object ID array over thread boundry.
Recreate an array with NSManagedObjects from the managed object IDs on the new thread with it's context.
I have made some Core Data helpers, following the rule of three (the third time you write something, make it general).
Most importantly I have hidden the complexity of managing different managed object contexts for each thread, handling notifications, and all that junk. Instead I have introduced the concept of thread local contexts. Basically lazily created NSManagedObjectContext instances that automatically registers for updates and cleanup when the current thread exits.
A normal use-case:
NSManagedObjectCotext* context = [NSManagedObjectCotext threadLocalContext];
// Do your stuff!!
NSError* error = nil;
if (![context saveWithFailureOption:NSManagedObjectContextCWSaveFailureOptionThreadDefault
error:&error])
{
// Handle error.
}
The full source code, including a sample app for parsing the news RSS from apple.com and store it in Core Data, is available here: https://github.com/jayway/CWCoreData.
There is no reason in this case to call reset on the background context because it will disappear anyway with the background thread and you never use it again in any case. You usually use reset with undo management when you want the context to forget previous steps.
Your major problem here is that your background thread is configured to receive notifications from the managed object context it creates. That is rather pointless.
Instead, you need to register the tableview controller (on the front thread) to receive the NSManagedObjectContextDidSaveNotification from the context on background thread. That way, when the background context saves, the front/main context will update itself with the new data which will trigger an update of the tableview.

Resources