I am using the method described in Core Data unique attributes to prevent having the same attribute twice (the attribute is called ID here).
This works fine in a single-threaded environment. I use a multi-threaded environment and use the one context per thread paradigm.
The problem is that if two threads simultaneously try to create an object with the same attribute, there is the following problem:
- Thread A creates the object with ID 1 in context1, there is no such object so it gets created
- Thread B creates the object with ID 1 in context2, there is no such object so it gets created
- Thread A synchronizes the context1->context2 (using the code below)
You find yourself with two records with the same ID (1).
I saw this problem when testing, so it is rare but will certainly happen over time.
Of course, there are multiple options, like GDC, semaphores to prevent this from happening but before going with a complex solution, I was wondering if someone has a better solution, or could recommend at what level to serialize the events.
Using iOS5+ and ARC.
I use this code for the synch of my contexts:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(backgroundContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:nil];
and:
- (void)backgroundContextDidSave:(NSNotification *)notification {
/* Make sure we're on the main thread when updating the main context */
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:@selector(backgroundContextDidSave:)
withObject:notification
waitUntilDone:NO];
return;
}
/* merge in the changes to the main context */
for (NSManagedObjectContext* context in [self.threadsDictionary allValues]){
[context mergeChangesFromContextDidSaveNotification:notification];
}
}
To get a thread-safe context:
/**
Returns the managed object context for the application.
If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
*/
- (NSManagedObjectContext *) managedObjectContextForThread {
// Per thread, give one back
NSString* threadName = [NSString stringWithFormat:@"%d",[NSThread currentThread].hash];
NSManagedObjectContext * existingContext = [self.threadsDictionary objectForKey:threadName];
if (existingContext==nil){
existingContext = [[NSManagedObjectContext alloc] init];
[existingContext setPersistentStoreCoordinator: [self persistentStoreCoordinator]];
[self.threadsDictionary setValue:existingContext forKey:threadName];
[existingContext setMergePolicy:NSOverwriteMergePolicy];
}
return existingContext;
}