NSManagedObjectContext - 导致死锁的子上下文

时间:2014-09-16 07:43:19

标签: core-data concurrency deadlock nsmanagedobjectcontext

我在Core Data中有一个父子孙核心数据上下文设置,如下所示。每当我尝试在孙子上下文上执行获取请求时,它都会导致线程上的死锁

- (NSManagedObjectContext *)defaultPrivateQueueContext
{
    if (!_defaultPrivateQueueContext) {
        _defaultPrivateQueueContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        _defaultPrivateQueueContext.persistentStoreCoordinator = self.persistentStoreCoordinator;
    }
    return _defaultPrivateQueueContext;
}

- (NSManagedObjectContext *)mainThreadContext {
    if (!_mainThreadContext) {
        _mainThreadContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        _mainThreadContext.parentContext = [self defaultPrivateQueueContext];
    }
    return _mainThreadContext;
}

+ (NSManagedObjectContext *)newPrivateQueueContext
{
    NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    context.parentContext = [[self sharedParliamentAPI] mainThreadContext];

    return context;
}

这是我的代码导致死锁(尝试执行获取请求时):

- (void)fetchMenuItemsWithCompletion:(void (^) (BOOL success, NSString *message))completionBlock {
    NSMutableURLRequest *request = [APIHelper createNewRequestWithURLExtension:@"menuitems" httpMethodType:@"GET" parameters:nil];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:self.sessionConfig];
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        NSObject *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
        if ([[json valueForKey:@"isSuccess"] boolValue]) {

            NSManagedObjectContext *defaultContext = self.defaultPrivateQueueContext;
            NSManagedObjectContext *privateQueueContext = [ParliamentAPI newPrivateQueueContext];

           [privateQueueContext performBlock:^{
                __block NSError *error;
                NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"MenuItem"];
                NSArray *fetchedRecords = [privateQueueContext executeFetchRequest:request error:&error];

                // do stuff with fetchedRecords

            }];
        } else {
            completionBlock([[json valueForKey:@"isSuccess"] boolValue], [json valueForKey:@"message"]);
        }
    }];
    [dataTask resume];
}

1 个答案:

答案 0 :(得分:2)

您的Core Data对象结构如下所示:

enter image description here

您有一个NSPrivateQueueConcurrencyType上下文作为连接到持久性商店协调员的根上下文,该协调器具有子NSMainQueueConcurrencyType上下文,而后者又具有NSPrivateQueueConcurrencyType上下文。许多写在IntarWebs上的人都推荐这种结构。

在您的情况下发生的事情是作为主队列上下文的私有队列上下文变得繁忙,这导致它的孩子等待。因为主队列上下文正在使用主队列进行所有工作,所以当发生这种情况时,它并不一定忙于执行核心数据工作(尽管这仍然有可能)。除了Core Data之外,主队列还做了很多工作,所有这些事情都会在需要与主队列上下文通信时影响孩子。

此外,允许使用NSMainQueueConcurrencyType创建的上下文执行核心数据操作,而无需明确使用performBlock:performBlockAndWait:。 例如,主队列上下文可以执行此操作:

NSArray *results    = nil;
NSError *error      = nil;
results = [mainQueueContext executeFetchRequest:fetchRequest error:&error];

并且不是必需来执行此操作:

[mainQueueContext performBlock:^{
    NSArray *results    = nil;
    NSError *error      = nil;
    results = [mainQueueContext executeFetchRequest:fetchRequest error:&error];
}];

这里的区别在于没有performBlock:的第一个示例将阻止等待结果的主线程。第二个,使用performBlock:,是异步的,不会阻塞 - 获取请求将被安排在队列上执行。显然,第一个例子可能会在任何子环境中引起一些争用。

如果您的配置具有NSMainQueueConcurrencyType上下文,而该上下文是另一个NSMainQueueConcurrencyType上下文的子项,那将是......糟糕的。它几乎可以保证在该配置中死锁。好消息是,你没有那个问题!

您可以将代码转换为使用带有performBlock:上下文的NSMainQueueConcurrencyType来缓解此部分问题。您还可以使用NSPrivateQueueConcurrencyType代替主队列上下文 - 根本没有充分理由使用主队列上下文。 NSFetchedResultsController can be used with a private queue context to do "background fetching"