CoreData 和并发:无法解释的行为

CoreData and Concurrency: Unexplained behavior

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

案例一 相同的托管对象在两个不同的地方不断变化,主线程和后台线程使用下面的代码。未执行管理对象内容保存。

观察:没有崩溃。我看到 "numberOfSales" 的值在 "main thread" 和 "background queue" 中读取的值不同。他们最终变得一样,分歧,变得一样等等。所以,我猜这是 "eventual consistency" 展示自己。

这是预期的行为吗?也就是说,从多个线程对同一托管对象上下文中的对象进行更改似乎没问题。

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

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

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

观察:没有崩溃。但我期望获取请求是串行的:一个来自主线程,一个来自后台 Q。但我看到只有其中一个执行。为什么会这样?

在后台执行的代码块 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]);
            }
        });
    }

如果您将 dispatch_async 用于并发核心数据代码,那么您已经做错了。它不会立即崩溃这一事实并不意味着什么。你已经越过写着 "Warning, killer dragons ahead" 的标志,而且没有龙吃掉你 的事实并不意味着你在做安全的事情。

如果您在多个线程或队列上使用 Core Data,您必须对[=24=使用performBlockperformBlockAndWait ]every 以任何方式触及 Core Data 的操作。这意味着您使用 NSMainQueueConcurrencyTypeNSPrivateQueueConcurrencyType 创建了托管对象上下文。此规则只有一个例外:如果您使用 NSMainQueueConcurrencyType 并且您 确定 您的代码来自主队列 运行,则您没有使用 performBlockperformBlockAndWait.

分析示例代码中的流程没有用;你严重违反了核心数据的并发规则,所以唯一真正重要的解释是它不一致,因为你做错了。