2

I have a very simple Core Data demo, in which there is only one button.

When I click the 'run' button, the App creates 10,000 objects in a for-loop, which is running in the global queue.

Update for more detail : If I put the for-loop in main thread, it runs well.

Update for my intent : I know that MOC is not thread-safe, but according to the Apple doc, we can also use serial queue to access the MOC, and the serial queue uses more than one threads.

Here I create the Core Data stack:

#pragma mark - Core Data Stack

- (NSManagedObjectContext *)managedObjectContext
{
    if (nil != _managedObjectContext) {
        return _managedObjectContext;
    }

    _managedObjectContext = [[NSManagedObjectContext alloc] init];

    if (self.persistentStoreCoordinator) {
        [_managedObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
    }

    return _managedObjectContext;
}

- (NSManagedObjectModel *)managedObjectModel
{
    if (nil != _managedObjectModel) {
        return _managedObjectModel;
    }

    _managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
    return _managedObjectModel;
}

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (nil != _persistentStoreCoordinator) {
        return _persistentStoreCoordinator;
    }

    NSString *storeType = NSSQLiteStoreType;
    NSString *storeName = @"model.sqlite";
    NSURL *storeURL = [NSURL fileURLWithPath:[[self applicationDocumentsDirectory] stringByAppendingPathComponent:storeName]];

    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];

    NSError *error = nil;
    if (![_persistentStoreCoordinator addPersistentStoreWithType:storeType
                                                   configuration:nil
                                                             URL:storeURL
                                                         options:nil
                                                           error:&error])
    {
        NSLog(@"Error : %@\n", [error localizedDescription]);
        NSAssert1(YES, @"Failed to create store %@ with NSSQLiteStoreType", [storeURL path]);
    }

    return _persistentStoreCoordinator;
}

#pragma mark -
#pragma mark Application's Documents Directory

- (NSString *)applicationDocumentsDirectory
{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
    return basePath;
}

after app has launched :

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.

    if (self.managedObjectContext) {
        ;
    }

    return YES;
}

When I click the button :

- (IBAction)runButtonDidClick:(id)sender
{
    /**
     * Access the moc using different threads to make deadlock.
     */

    [self runSave];
}

- (void)runSave
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
        NSManagedObjectContext *moc = appDelegate.managedObjectContext;

        if (moc) {
            for (int j = 0; j < 10000; ++j) {
                People *people = [NSEntityDescription insertNewObjectForEntityForName:@"People" inManagedObjectContext:moc];
                people.name = @"noname";
            }

            NSLog(@"**********IN SAVE %@", [NSThread currentThread]);
            NSError *error = nil;
            if ([moc save:&error]) {
                ;
            }

            NSLog(@"**********OUT SAVE %@", [NSThread currentThread]);
        }
    });
}

For clicking the run button some times, maybe 2 or 3 or 4... It crashes

I could not figure out why... Thanks for any help.

enter image description here

enter image description here

Jason Lee
  • 3,200
  • 1
  • 34
  • 71
  • A NSManagedObjectContext is not thread-safe. Why do you intentionally use it on a *concurrent* queue, so that the same MOC will be used on different threads? - And how are we to understand the comment *"Access the moc using different threads to make deadlock."* ?? - Btw. I do not see where you actually create a persistent store. – Martin R Jun 06 '13 at 09:46
  • @MartinR Thanks. I actually intentionally use the MOC in different threads & The sqlite file has been created. I know that MOC is not thread-safe, but here, I just access the MOC when the button is clicked. One access, one thread, similar to serial queue? – Jason Lee Jun 06 '13 at 09:59
  • @JasonLee right - thread does not matter to MOC but it should be always the same thread – Jakub Jun 06 '13 at 10:04
  • `dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)` returns a *concurrent* queue, not a serial queue. A concurrent queue uses *multiple threads* to execute things concurrently. – Martin R Jun 06 '13 at 10:06
  • @SimpleMan yes, but threads can access the MOC one by one, right? – Jason Lee Jun 06 '13 at 10:11
  • @MartinR you're right. But here, the global queue will only dispatch one task when the run button was clicked once :) And the concurrent queue will run one task on one thread, right? – Jason Lee Jun 06 '13 at 10:13

1 Answers1

3

Core data should be always work on thread witch have moc. the only job for performBlock and performBlockAndWait is that take care of thread safety. With it inserting to Core Data will always running in the right thread. You can define moc on whatever thread you want - performBlock always choose the right one.

So:

[self.managedObjectContext performBlock:^{
            for(NSDictionary *dic in arr) {
                //inserting here!
            }
}];

In your case:

- (void)runSave
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
        NSManagedObjectContext *moc = appDelegate.managedObjectContext;

        if (moc) {

          [moc performBlock:^{
            for (int j = 0; j < 10000; ++j) {
                People *people = [NSEntityDescription insertNewObjectForEntityForName:@"People" inManagedObjectContext:moc];
                people.name = @"noname";
            }
            NSError *error = nil;
            if ([moc save:&error]) {
                ;
            }
         }];
        }
    });
}
Jakub
  • 13,712
  • 17
  • 82
  • 139
  • thanks But how about serial queue? The serial queue is using more than one thread. – Jason Lee Jun 06 '13 at 09:56
  • I add this to my answer: the only job for `performBlock` and `performBlockAndWait` is that take care of thread safety. With it inserting to Core Data will always running in the right thread. You can define moc on whatever thread you want - `performBlock` always choose the right one. – Jakub Jun 06 '13 at 10:00