我遇到了同样的死锁问题(在SO上很常见),发生在多个NSManagedObjectContexts&多线程场景。在我的一些视图控制器中,我的应用程序使用后台线程从Web服务获取数据,并在同一个线程中保存它。在其他情况下,在没有保存的情况下不进一步进展是有意义的(例如,当他们点击“下一步”时表单中的持久值),保存在主线程上完成。 AFAIK在理论上应该没有任何问题,但偶尔我可以通过调用
来解决僵局if (![moc save:&error])
...当发生死锁时,这似乎总是在后台线程的保存中。每次通话都不会发生;事实上,情况正好相反,我必须使用我的应用程序几分钟然后才会发生。
我已经阅读了我能找到的所有帖子以及Apple文档等,我确信我正在遵循这些建议。具体而言,我对使用多个MOC /线程的理解归结为:
前段时间我在this SO thread上遇到了一些MOC助手类的代码,发现它很容易理解并且使用起来非常方便,因此我现在所有的MOC交互都是通过它进行的。这是我的ManagedObjectContextHelper类的全部内容:
#import "ManagedObjectContextHelper.h"
@implementation ManagedObjectContextHelper
+(void)initialize {
[[NSNotificationCenter defaultCenter] addObserver:[self class]
selector:@selector(threadExit:)
name:NSThreadWillExitNotification
object:nil];
}
+(void)threadExit:(NSNotification *)aNotification {
TDAppDelegate *delegate = (TDAppDelegate *)[[UIApplication sharedApplication] delegate];
NSString *threadKey = [NSString stringWithFormat:@"%p", [NSThread currentThread]];
NSMutableDictionary *managedObjectContexts = delegate.managedObjectContexts;
[managedObjectContexts removeObjectForKey:threadKey];
}
+(NSManagedObjectContext *)managedObjectContext {
TDAppDelegate *delegate = (TDAppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *moc = delegate.managedObjectContext;
NSThread *thread = [NSThread currentThread];
if ([thread isMainThread]) {
[moc setMergePolicy:NSErrorMergePolicy];
return moc;
}
// a key to cache the context for the given thread
NSString *threadKey = [NSString stringWithFormat:@"%p", thread];
// delegate.managedObjectContexts is a mutable dictionary in the app delegate
NSMutableDictionary *managedObjectContexts = delegate.managedObjectContexts;
if ( [managedObjectContexts objectForKey:threadKey] == nil ) {
// create a context for this thread
NSManagedObjectContext *threadContext = [[NSManagedObjectContext alloc] init];
[threadContext setPersistentStoreCoordinator:[moc persistentStoreCoordinator]];
[threadContext setMergePolicy:NSErrorMergePolicy];
// cache the context for this thread
NSLog(@"Adding a new thread:%@", threadKey);
[managedObjectContexts setObject:threadContext forKey:threadKey];
}
return [managedObjectContexts objectForKey:threadKey];
}
+(void)commit {
// get the moc for this thread
NSManagedObjectContext *moc = [self managedObjectContext];
NSThread *thread = [NSThread currentThread];
if ([thread isMainThread] == NO) {
// only observe notifications other than the main thread
[[NSNotificationCenter defaultCenter] addObserver:[self class] selector:@selector(contextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:moc];
}
NSError *error;
if (![moc save:&error]) {
NSLog(@"Failure is happening on %@ thread",[thread isMainThread]?@"main":@"other");
NSArray* detailedErrors = [[error userInfo] objectForKey:NSDetailedErrorsKey];
if(detailedErrors != nil && [detailedErrors count] > 0) {
for(NSError* detailedError in detailedErrors) {
NSLog(@" DetailedError: %@", [detailedError userInfo]);
}
}
NSLog(@" %@", [error userInfo]);
}
if ([thread isMainThread] == NO) {
[[NSNotificationCenter defaultCenter] removeObserver:[self class] name:NSManagedObjectContextDidSaveNotification
object:moc];
}
}
+(void)contextDidSave:(NSNotification*)saveNotification {
TDAppDelegate *delegate = (TDAppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *moc = delegate.managedObjectContext;
[moc performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:)
withObject:saveNotification
waitUntilDone:NO];
}
@end
以下是多线程位的片段,它似乎陷入僵局:
NSManagedObjectID *parentObjectID = [parent objectID];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
// GET BACKGROUND MOC
NSManagedObjectContext *backgroundContext = [ManagedObjectContextHelper managedObjectContext];
Parent *backgroundParent = (Parent*)[backgroundContext objectWithID:parentObjectID];
// HIT THE WEBSERVICE AND PUT THE RESULTS IN THE PARENT OBJECT AND ITS CHILDREN, THEN SAVE...
[ManagedObjectContextHelper commit];
dispatch_sync(dispatch_get_main_queue(), ^{
NSManagedObjectContext *mainManagedObjectContext = [ManagedObjectContextHelper managedObjectContext];
parent = (Parent*)[mainManagedObjectContext objectWithID:parentObjectID];
});
});
错误中的conflictList似乎表明它与父对象的ObjectID有关:
conflictList = (
"NSMergeConflict (0x856b130) for NSManagedObject (0x93a60e0) with objectID '0xb07a6c0 <x-coredata://B7371EA1-2532-4D2B-8F3A-E09B56CC04F3/Child/p4>'
with oldVersion = 21 and newVersion = 22
and old object snapshot = {\n parent = \"0xb192280 <x-coredata://B7371EA1-2532-4D2B-8F3A-E09B56CC04F3/Parent/p3>\";\n name = \"New Child\";\n returnedChildId = 337046373;\n time = 38;\n}
and new cached row = {\n parent = \"0x856b000 <x-coredata://B7371EA1-2532-4D2B-8F3A-E09B56CC04F3/Parent/p3>\";\n name = \"New Child\";\n returnedChildId = 337046373;\n time = 38;\n}"
);
我已经尝试在我获得MOC时立即调用refreshObject,理论上如果这是我们之前使用过的MOC(例如我们之前在主线程上使用了MOC)它可能与助手类给我们的相同,然后在另一个线程中保存可能意味着我们需要显式刷新。但它没有任何区别,如果我长时间点击,它仍然会陷入僵局。
有没有人有任何想法?
编辑:如果我为所有例外设置了断点,则调试器会在if (![moc save:&error])
行自动暂停,因此播放/暂停按钮已暂停并显示播放三角形。如果我禁用所有异常的断点,那么它实际记录冲突并继续 - 可能是因为合并策略当前设置为NSErrorMergePolicy - 所以我不认为它实际上是在线程上死锁。
答案 0 :(得分:3)
我根本不推荐你的做法。首先,除非您仅限于iOS4,否则您应该使用MOC并发类型,而不是旧方法。即使在iOS 5下(针对嵌套上下文也是如此),performBlock
方法更加健全。
另请注意,dispatch_get_global_queue
提供并发队列,不能用于同步。
修改强>
您正在尝试手动管理MOC和线程。如果你愿意,你可以做到,但你的道路上有龙。这就是创建新方法的原因,以最大限度地减少跨多个线程使用Core Data时出错的可能性。每当我看到使用Core Data进行手动线程管理时,我总是建议改变第一种方法。这将立即消除大多数错误。
我不需要看到比手动映射MOC和线程更多的东西,知道你在寻找麻烦。只需重新阅读该文档,并以正确的方式执行(使用performBlock
)。