CoreData和并发:原因不明的行为

时间:2016-07-25 08:52:59

标签: objective-c multithreading core-data grand-central-dispatch

我听说过很多关于CoreData和并发的问题。因此,我决定使用虚拟代码尝试一些场景。我无法完全解释所有观察结果。任何指针都将非常感激。

案例1 使用下面的代码在两个不同的位置,主线程和后台线程中连续更改相同的托管对象。不执行托管对象内容保存。

观察:没有崩溃。我看到" numberOfSales"的价值在"主线程"读取的内容之间是不同的。并在"后台队列"。他们最终变得相同,分歧,变得相同等等。所以,我猜这是"最终的一致性"参展。

这是预期的行为吗?即,可以从多个线程更改同一托管对象上下文中的对象。

案例2 这两段代码还执行托管对象上下文到持久存储的保存

观察:随机崩溃。这是否意味着真正的问题是当您尝试从多个线程将事物存储到持久存储时?

案例3 我使用串行队列来序列化获取请求。如下面的代码示例2所示。

观察:没有崩溃。但我期待连续获取请求:一个来自主线程,一个来自后台Q.但我看到只有一个执行。为什么会这样?

在后台执行的代码块

dispatch_async(backgroundQueue, ^(void) {
            while (1) {
                sleep(1);
                NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
                NSEntityDescription *entity = [NSEntityDescription entityForName:@"Person"
                                                          inManagedObjectContext:self.managedObjectContext];
                [fetchRequest setEntity:entity];
                NSError *error;
                NSArray *fetchedObjects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
                for (Person *info in fetchedObjects) {
                    NSLog(@"BackQ Name: %@, SSN: %@, Num sales = %@", info.name,info.ssn, info.numberOfSales);
                    info.numberOfSales = @(2);
                }

             //In case 1: The save to persistent store part below is commented out, in case 2: this part exists

                if (![self.managedObjectContext save:&error]) {
                    NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]);
                }
            }
    });

在主线程中执行的代码块

while (1) {
        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
        NSEntityDescription *entity = [NSEntityDescription entityForName:@"Person"
                                                  inManagedObjectContext:self.managedObjectContext];
        [fetchRequest setEntity:entity];
        [fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"(name = %@)",@"XXX"]] ;
        NSError *error;
        NSArray <Person *>*fetchedObjects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
        fetchedObjects[0].numberOfSales = @([fetchedObjects[0].numberOfSales integerValue] + 1);
        NSLog(@"Fore Name: %@, SSN: %@, Num sales = %@", fetchedObjects[0].name,fetchedObjects[0].ssn, fetchedObjects[0].numberOfSales);

        //In case 1: The save to persistent store part below is commented out, in case 2: this part exists

        if (![self.managedObjectContext save:&error]) {
            NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]);
        }
    }

代码示例2

   self.coreDataQ = dispatch_queue_create("com.smarthome.coredata.bgqueue2", DISPATCH_QUEUE_SERIAL);

代码示例2:背景Q中的代码

        dispatch_async(backgroundQueue, ^(void) {
                while (1) {
                    dispatch_async(self.coreDataQ, ^(void) {
                        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
                        NSEntityDescription *entity = [NSEntityDescription entityForName:@"Person"
                                                                  inManagedObjectContext:self.managedObjectContext];
                        [fetchRequest setEntity:entity];
                        NSError *error;
                        NSArray *fetchedObjects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
                        for (Person *info in fetchedObjects) {
                            NSLog(@"BackQ Name: %@, SSN: %@, Num sales = %@", info.name,info.ssn, info.numberOfSales);
                            info.numberOfSales = @(2);
                        }
                        if (![self.managedObjectContext save:&error]) {
                            NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]);
                        }
                    });
                }
        });

代码示例2:主线程中的代码

   while (1) {
        dispatch_async(self.coreDataQ, ^(void) {
            NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
            NSEntityDescription *entity = [NSEntityDescription entityForName:@"Person"
                                                      inManagedObjectContext:self.managedObjectContext];
            [fetchRequest setEntity:entity];
            [fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"(name = %@)",@"XXX"]] ;
            NSError *error;
            NSArray <Person *>*fetchedObjects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
            fetchedObjects[0].numberOfSales = @([fetchedObjects[0].numberOfSales integerValue] + 1);
            NSLog(@"Fore Name: %@, SSN: %@, Num sales = %@", fetchedObjects[0].name,fetchedObjects[0].ssn, fetchedObjects[0].numberOfSales);
            if (![self.managedObjectContext save:&error]) {
                NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]);
            }
        });
    }

1 个答案:

答案 0 :(得分:2)

如果您使用dispatch_async并发核心数据代码,那么您已经做错了。它不会立即崩溃的事实并不意味着什么;你已经超越了“警告,前方杀手龙”的迹象,以及没有龙吃过你的事实并不意味着你正在做一些安全的事情。

如果您在多个线程或队列中使用Core Data,必须使用performBlockperformBlockAndWait进行每次操作以任何方式触及Core Data。这意味着您使用NSMainQueueConcurrencyTypeNSPrivateQueueConcurrencyType创建了托管对象上下文。此规则只有一个例外:如果您使用NSMainQueueConcurrencyType并且某些您的代码是从主队列运行的,那么您不必使用performBlockperformBlockAndWait

分析示例代码中的流程没有用;您严重违反了Core Data的并发规则,因此唯一真正重要的解释是它不一致,因为您做错了。