2014-02-26 24 views
10

我遇到了無法解析的Core Data問題。我已經很難了解Core Data中的併發問題,因此我非常小心,只能在performBlock:performBlockAndWait:塊中執行任何核心數據操作。在performBlockAndWait塊內執行提取請求時核心數據發生死鎖

這裏去我的代碼:

/// Executes a fetch request with given parameters in context's block. 
+ (NSArray *)executeFetchRequestWithEntityName:(NSString *)entityName 
           predicate:(NSPredicate *)predicate 
           fetchLimit:(NSUInteger)fetchLimit 
          sortDescriptor:(NSSortDescriptor *)sortDescriptor 
           inContext:(NSManagedObjectContext *)context{ 
    NSCAssert(entityName.length > 0, 
      @"entityName parameter in executeFetchRequestWithEntityName:predicate:fetchLimit:sortDescriptor:inContext:\ 
      is invalid"); 

    __block NSArray * results = nil; 

    NSPredicate * newPredicate = [CWFCoreDataUtilities currentUserPredicateInContext:context]; 
    if (predicate){ 
     newPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:@[newPredicate, predicate]]; 
    } 

    [context performBlockAndWait:^{ 

     NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:entityName]; 
     request.fetchLimit = fetchLimit; 
     request.predicate = newPredicate; 
     if (sortDescriptor) { 
      request.sortDescriptors = @[sortDescriptor]; 
     } 

     NSError * error = nil; 
     results = [context executeFetchRequest:request error:&error]; 

     if (error){ 
      @throw [NSException exceptionWithName:NSInternalInconsistencyException 
              reason:@"Fetch requests are required to succeed."  
             userInfo:@{@"error":error}]; 
      NSLog(@"ERROR! %@", error); 
     } 

     NSCAssert(results != nil, @"Fetch requests must succeed"); 
    }]; 

    return results; 
} 

當我從兩個不同的線程同時進入該方法,並通過兩個不同的環境中,我導致該行死鎖:results = [context executeFetchRequest:request error:&error];

這是有趣的:似乎兩個線程都無法獲得持久存儲協調器上的一些鎖定,以執行獲取請求。

我所有的上下文都是NSPrivateQueueConcurrencyType

我不能把我的手指,爲什麼我鎖定應用程序,我應該做什麼不同。我對Stack Overflow的研究沒有給我什麼,因爲大多數人通過在MOC的隊列上分派獲取請求來修復所有的鎖,我已經這樣做了。

我會很感激這個問題的任何信息。隨意提供文檔鏈接和其他長篇文章:我渴望更多地瞭解各種併發問題和策略。

+0

你能提供代碼:'currentUserPredicateInContext:'和'predicate'建設(他們使用從其他上下文對象中獲取構造)? –

+1

你確定這是一個僵局嗎?我建議你發佈線程的堆棧痕跡 –

+0

@DanShelly該錯誤很難重現,我今天沒有看到它,所以沒有堆棧跟蹤。 謂詞格式爲「owner =%@」,其中%@是來自用戶默認值的字符串。我根本沒有在謂詞中使用任何MObjects。 我相信這是一個死鎖,因爲在堆棧跟蹤中,我的兩個線程停留在mutex_wait上,這是對executeFetchRequest的調用所致。 –

回答

0

如果您有興趣瞭解關於核心數據(和線程)的更多信息,以下網站將非常有幫助。我參加馬修·莫雷在亞特蘭大CocoaConf 2013年

高性能核心數據(http://highperformancecoredata.com

說話的同伴示例代碼的網站,請訪問:https://github.com/mmorey/MDMHPCoreData

所有你需要在你的應用程序做,就是有一個MDMPersistenceStack類的Singleton實例(某處)。

就您的問題而言,即使AppleManualManualObjectContext類的文檔確實允許以您擁有的方式(在覈心數據操作在塊中的NSManagedObjectContext實例上執行)編寫代碼,並且在某種程度上鼓勵 - 我想指出這不是唯一的方法。

每當我修復了具有核心數據併發問題(鎖定)的應用程序時,我認爲最簡單的事情就是在我想要執行Core Data操作的線程或塊內創建私有NSManagedObjectContext。

它可能不是最優雅的方法,但它永遠不會失敗。一個總是保證NSManagedObjectContext是在同一個線程中創建和執行的,因爲NSManagedObjectContext是明確創建的。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 

dispatch_async(queue, ^{ 
     /* 
     Create NSManagedObjectContext 
     Concurrency of Managed Object Context should be set to NSPrivateQueueConcurrencyType 
     If you use the MDMPersistenceStack class this is handled for you. 
     */ 
     NSManagedObjectContext *managedObjectContext = .... 

     /* 
     Call the method that you have listed in your code. 
     Let's assume that this class method is in MyClass 
     Remove the block that you have in your method, as it's not needed 
     */ 
     [MyClass executeFetchRequestWithEntityName: ......] // rest of parameters 
}); 
+0

獲取的其他上下文的孩子。這是一個非常糟糕的答案。首先,如[NSManagedObjectContext的類引用:](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/CoreDataFramework/Classes/NSManagedObjectContext_Class/NSManagedObjectContext.html)中所述 專用隊列( NSPrivateQueueConcurrencyType) - 上下文創建並管理專用隊列。 (...)使用基於隊列的併發類型與performBlock:and performBlockAndWait :.一起使用上下文。您將「標準」消息分組發送到塊中的上下文以傳遞給這些方法之一。 –

+0

因此,我對這些塊所做的工作是處理NSPrivateQueueConcurrencyType上下文的併發性的正確方法,並且您提出的建議可能是您使用NSConfinementConcurrencyType上下文來完成的方式。 –

+0

我們必須同意不同意,我的答案是一個不好的答案。 :-)也許你不喜歡那部分 - 我試圖簡化的東西。在iOS 4之前的世界裏,這將是一個傑出的答案。 – kurtn718

2

這是有趣:它看起來像兩個線程不能以執行爲獲取請求獲取關於持久性存儲協調員一些鎖。

持久存儲協調器是一個串行隊列。如果一個上下文訪問它,另一個上下文將被阻止。

Apple Docs

協調員辦提供的另一併發他們序列化操作。如果你想使用多個線程進行不同的寫操作,你可以使用多個協調器。請注意,如果多個線程直接與協調器一起工作,他們需要明確地鎖定和解鎖。

如果您需要同時執行多個後臺提取請求,您將需要多個持久存儲協調器。

多個協調器將使您的代碼稍微複雜一些,但應儘可能避免。你真的需要同時進行多次抓取嗎?你可以做一個更大的抓取,然後過濾內存中的結果嗎?

-1

我這樣做了。它爲我解決了這個問題。我也遇到了很多的僵局。看看它是否適合你。

+ (NSArray *)getRecordsForFetchRequest:(NSFetchRequest *)request inContext:(NSManagedObjectContext *)context 
{ 


@try 
{ 
    __weak __block NSError *error = nil; 

    __block __weak NSArray *results = nil; 


    [context performBlockAndWait:^{ 
     [context lock]; 

     results = [context executeFetchRequest:request error:&error]; 

     [context processPendingChanges]; 

     [context unlock]; 

    }]; 

    [self handleErrors:error]; 

    request = nil; 
    context = nil; 

    return results; 
} 
@catch (NSException *exception) 
{ 
    if([exception.description rangeOfString:@"Can only use -performBlockAndWait: on an NSManagedObjectContext that was created with a queue"].location!=NSNotFound) 
    { 
     NSError *error = nil; 

     [context lock]; 

     __weak NSArray *results = [context executeFetchRequest:request error:&error]; 

     [context processPendingChanges]; 

     [context unlock]; 

     [self handleErrors:error]; 

     request = nil; 
     context = nil; 

     return results; 
    } 

    return nil; 
} 

}

+0

不應該在Objective-C中的常規代碼中使用異常處理。邏輯或內部錯誤只會引發異常,並且不應發生在常規實際情況中。具體而言,您應該在這裏創建兩個方法:一個用於獲取使用專用隊列配置的上下文的結果,另一個用於其他上下文。 – Frizlab