4

我遇到了在多 NSManagedObjectContexts 和多线程场景中发生的相同死锁问题(这在 SO 上很常见)。在我的一些视图控制器中,我的应用程序使用后台线程从 Web 服务获取数据,并在同一个线程中保存它。在其他情况下,如果不保存就不会继续前进(例如,当他们点击“下一步”时从表单中保留值),保存是在主线程上完成的。AFAIK理论上应该没有错,但有时我可以在调用

if (![moc save:&error])

...当死锁发生时,这似乎总是在后台线程的保存中。并非每次通话都会发生这种情况。事实上恰恰相反,我必须使用我的应用程序几分钟,然后它就会发生。

我已经阅读了我能找到的所有帖子以及 Apple 文档等,我确信我正在遵循这些建议。具体来说,我对使用多个 MOC/线程的理解归结为:

  • 每个线程必须有自己的 MOC。
  • 必须在该线程上创建线程的 MOC(而不是从一个线程传递到另一个线程)。
  • 一个 NSManagedObject 不能被传递,但是一个 NSManagedObjectID 可以,并且你使用这个 ID 来使用不同的 MOC 来膨胀一个 NSManagedObject。
  • 如果它们都使用相同的 PersistentStoreCoordinator,则必须将来自一个 MOC 的更改合并到另一个 MOC。

不久前,我在这个 SO 线程上遇到了一些 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 - 所以我认为它实际上并没有在线程上死锁。这是暂停时两个线程状态的屏幕截图。

4

1 回答 1

3

我根本不推荐你的方法。首先,除非你局限于 iOS4,否则你应该使用 MOC 并发类型,而不是旧方法。即使在 iOS 5 下(嵌套上下文已被破坏),该performBlock方法也更加合理。

另外,注意dispatch_get_global_queue提供了一个并发队列,不能用于同步。

详细信息可在此处找到:http: //developer.apple.com/library/ios/#documentation/cocoa/conceptual/CoreData/Articles/cdConcurrency.html

编辑

您正在尝试手动管理 MOC 和线程。如果你愿意,你可以做到,但你的道路上有龙。这就是创建新方法的原因,以最大程度地减少跨多个线程使用 Core Data 时出错的机会。每当我看到使用 Core Data 进行手动线程管理时,我总是建议将更改作为第一种方法。这将立即消除大多数错误。

除了您手动映射 MOC 和线程之外,我不需要看到更多内容,就知道您是在自找麻烦。只需重新阅读该文档,并以正确的方式进行操作(使用performBlock)。

于 2013-04-21T20:55:52.710 回答