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允許從多個線程訪問。核心數據的塊方法**是通過串行隊列串行化請求,所以它實際上並不複雜,只是不同而已。 –