NSManagedObjectContext - 导致死锁的子上下文

Posted

技术标签:

【中文标题】NSManagedObjectContext - 导致死锁的子上下文【英文标题】:NSManagedObjectContext - Child Context causing deadlock 【发布时间】:2014-09-16 07:43:18 【问题描述】:

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

- (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];

【问题讨论】:

mainThreadContext 有什么用? everything 是否通过 performBlock 或 performBlockAndWait 使用该上下文?如果没有,那可能会导致孩子陷入僵局。 在执行此代码时,未使用 mainThreadContext。是的,所有使用该上下文的东西都是通过 performBLock 如果将根上下文设置为新子上下文的父级,消除主线程上下文,是否还死锁? 当我将新子上下文的父级设置为根上下文时,死锁消失了。但是,这样做不会在进行更改时更新 mainThreadContext(因为它是根上下文的单独子项) 【参考方案1】:

您的核心数据对象结构如下所示:

你有一个NSPrivateQueueConcurrencyType 上下文作为连接到持久存储协调器的根上下文,它有一个子NSMainQueueConcurrencyType 上下文,它又具有一个NSPrivateQueueConcurrencyType 上下文。许多在 IntarWebs 上写作的人都推荐这种结构。

在您的情况下发生的情况是,作为主队列上下文的私有队列上下文变得繁忙,这导致它的子队列等待。因为主队列上下文正在使用主队列来完成它的所有工作,所以当这种情况发生时,它不一定忙于做 Core Data 工作(尽管这仍然有点可能)。除了 Core Data 之外,主队列还做了很多工作,所有这些事情最终都会影响到子队列需要与主队列上下文通信的任何时候。

此外,使用NSMainQueueConcurrencyType 创建的上下文可以在不显式使用performBlock:performBlockAndWait: 的情况下执行Core Data 操作。 例如,主队列上下文可以这样做:

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".

【讨论】:

感谢您的深入解答。我试试看

以上是关于NSManagedObjectContext - 导致死锁的子上下文的主要内容,如果未能解决你的问题,请参考以下文章

NSManagedObjectContext:撤消保存操作?

声明 NSManagedObjectContext 时出错

CoreData 多 NSManagedObjectContext 保存通知说明

NSManagedObjectContext 类别

NSManagedObjectContext: performBlockAndWait vs performBlock 通知中心

如何清除 NSManagedObjectContext 中的所有对象?