0

我听到很多关于CoreData和并发性的问题。因此,我决定尝试使用虚拟代码的一些场景。我无法完全解释所有的观察结果。任何指针将不胜感激。核心数据和并发:无法解释的行为

案例1 相同的管理对象被连续在两个不同的地方,主线程和用下面的代码后台线程改变。托管对象内容保存不会执行。

观察:没有崩溃。我看到“numberOfSales”的值在“主线程”和“背景队列”中读取的值不同。他们最终变得一样,分歧,变得一样等等。所以,我猜这是“最终一致性”展现自己。

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

案例2 的两段代码也节省了管理对象上下文中执行到持久性存储

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

案例3 我通过使用串行队列序列化获取请求。显示在下面的代码示例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]); 
      } 
     }); 
    } 
+0

您不能直接在后台线程中使用objectContext,如果您想实现这一点,您必须创建具有后台线程能力的子上下文。 – ogres

+0

是的,这是我在CoreData编程指南中看到的理论。但我在实践中没有看到这一点。事实上,在代码示例2中,只有“后台队列块”中的NSLOg正在执行。 –

+0

打开并发断言,如http://oleb.net/blog/2014/06/core-data-concurrency-debugging/中所示 - 实际上,读取整个事件 – jrturton

回答

2

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

如果你在一个以上的线程或队列使用核心数据,你必须使用无论performBlockperformBlockAndWait操作触及以任何方式核心数据。这意味着您使用NSMainQueueConcurrencyTypeNSPrivateQueueConcurrencyType创建了托管对象上下文。此规则只有一个例外:如果您使用的是NSMainQueueConcurrencyType,并且您是某些您的代码正在主队列中运行,则不必使用performBlockperformBlockAndWait

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

+0

谢谢,汤姆。我只是好奇,为什么设计不如1那么简单。如果你正在访问不同的表,或者你只是在读数据,那么可以同时做多个线程。 3.如果要写入多个表,那么如果要通过串行队列序列化所有请求,请执行此操作。而不是所有这些并发症? –

+0

因为核心数据不是线程安全的,所以允许你的第一点,但是这个API允许从多个线程访问。核心数据的块方法**是通过串行队列串行化请求,所以它实际上并不复杂,只是不同而已。 –