ManagedObjectContext performBlock(AndWait)死锁

时间:2014-04-08 20:02:14

标签: ios core-data concurrency save deadlock

我之前已经看过这个问题,但是没有一个解决方案似乎对我的情况有任何影响,这就是:

我的应用使用三个ManagedObjectContexts:

1)在全局(后台)队列上创建" diskManagedObjectContext",带有NSPrivateQueueConcurrencyType且没有父上下文,用于在后台线程上将上下文更改写入磁盘(持久存储):

-(NSManagedObjectContext *)diskManagedObjectContext
{
    if (! _diskManagedObjectContext)
    {
        dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
        ^{
            _diskManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
            _diskManagedObjectContext.persistentStoreCoordinator = self.ircManagedObjectLibrary.persistentStoreCoordinator;
        });
    }

    return _diskManagedObjectContext;
}

2)带有NSMainQueueConcurrencyType的" mainManagedObjectContext"将diskManagedObjectContext作为其父上下文,在主线程上创建(在应用程序启动时),并由所有GUI进程(视图控制器等)使用。 )。保存在mainManagedObjectContext上只需按下更改" up"到其父级diskManagedObjectContext,然后将异步写入磁盘。

-(NSManagedObjectContext *)mainManagedObjectContext
{
    if (! _mainManagedObjectContext)
    {
        _mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        _mainManagedObjectContext.undoManager = nil;
        _mainManagedObjectContext.parentContext = self.diskManagedObjectContext;
    }

    return _mainManagedObjectContext;
}

3)带有NSPrivateQueueConcurrencyType的" backgroundManagedObjectContext"将mainManagedObjectContext作为其父上下文,在全局(后台)队列上创建,并由所有非GUI进程使用(数据存档,日志记录,等)在后台进行相对低优先级的模型更改。保存在backgroundManagedObjectContext上只需按下更改" up"在其父级mainManagedObjectContext中,此时监听与其相关的模型更改的GUI元素获取事件并相应地更新。

-(NSManagedObjectContext *)backgroundManagedObjectContext
{
    if (! _backgroundManagedObjectContext)
    {
        dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
        ^{
            _backgroundManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
            _backgroundManagedObjectContext.undoManager = nil;
            _backgroundManagedObjectContext.parentContext = self.mainManagedObjectContext;
        });
    }

    return _backgroundManagedObjectContext;
}

我已经实现了上面描述的嵌套保存行为(backgroundMOC - (sync) - > mainMOC - (async) - > diskMOC - (async) - > disk)因此,作为一种方法对我说了什么打电话给" ManagedObjectLibrarian" class(其中有两个实例,一个包含并封装mainMOC,另一个包含backgroundMOC):

-(BOOL)saveChanges:(NSError **)error
{
    __block BOOL successful = NO;

*[DEADLOCKS HERE]*  [self.managedObjectContext performBlockAndWait:  *[SYNCHRONOUS]*
    ^{
        NSError *internalError = nil;

        // First save any changes on the managed object context of this ManagedObjectLibrarian. If the context has a parent, this does not write changes to disk but instead simply pushes the changes to the parent context in memory, and so is very fast.

        successful = [_managedObjectContext save:&internalError];

        if (successful)
        {
           // If successful, then if the context of this ManagedObjectLibrarian has a parent with private queue concurrency (which only the app delegate's mainIrcManagedObjectLibrarian does), save the changes on that parent context, which if it does not itself have a parent will write the changes to disk. Because this write is performed as a block operation on a private queue, it is executed in a non-main thread.

           if (_managedObjectContext.parentContext)
           {
              [_managedObjectContext.parentContext performBlock:  *[ASYNCHRONOUS]*
              ^{
                  if (_managedObjectContext.parentContext.concurrencyType == NSPrivateQueueConcurrencyType)
                  {
                      NSError *parentError = nil;
                      BOOL parentSuccessful = [_managedObjectContext.parentContext save:&parentError];

[Error handling, etc.]

这些嵌套保存中的第一个是在performBlockAndWait中执行的,原因如下:

1)它遵循MOC自己的save方法的模式,该方法返回BOOL成功结果(因此在初始保存完成或失败之前不得返回)。 2)由于mainMOC和backgroundMOC都有父上下文,因此它们的保存仅将更改推送到父项,因此非常快(因此不需要异步执行,与diskMOC保存不同)。

(如果它很重要:我执行后台ManagedObjectLibrarian和主ManagedObjectLibrarian保存在performBlock(AndWait)中的原因:首先在它们各自的MOC上,以便我可以根据需要从任何线程执行这些保存。)< / p>

我认为所有这些都是非常典型的,而且希望如此,这么好。

[序言结尾]

问题在于:我在

开始时遇到明显的僵局
 [self.managedObjectContext performBlockAndWait:  *[SYNCHRONOUS]*

位于此saveChanges方法的顶部。这种情况正在发生,即使对此方法的调用 not 本身也是在performBlockAndWait:block中执行的 - 事实上,我遇到的第一个死锁是在app启动时,在主线程上调用以下内容时:

[self.backgroundManagedObjectLibrarian saveChanges:nil];

查看线程状态,当发生此死锁时,另一个阻塞线程在调用MOC的save:方法时对其执行死锁:

[self.dataSourceProperties.managedObjectContext performBlock: *[ASYNCHRONOUS]*
^{
    self.dataSourceProperties.dataArchiving.isEnabled = [NSNumber numberWithBool:_dataArchivingIsEnabled];

*[DEADLOCKS HERE]* [self.dataSourceProperties.managedObjectContext save:nil];
}];

这里保存的dataArchiving.isEnabled模型实体属性更改是在peformBlockAndWait中进行的:在同一个backgroundManagedObjectLibrarian上; 上面调用saveChanges:在backgroundManagedObjectLibrarian上,之外的以及之后的块返回。所以我不知道同步(peformBlockAndWait :)块中的早期异步(peformBlock :)保存是如何阻止同步块之外的后续同步(peformBlockAndWait :)保存。

还有什么,如果我将这个模型 - 实体 - 属性 - 更改保存操作从performBlock更改为performBlockAndWait:,则不会发生死锁(好吧,直到执行到达下一个异步模型实体属性) - 改变保存)。

我想我当然可以通过并使我的所有异步模型 - 实体 - 属性 - 更改保存同步,但我觉得我不应该这样做,特别是因为我没有&#39;这些变化需要立即传播,也不要留下任何回报或结果。

[问题]

所以,问题:

1)为什么我在这里遇到僵局? 2)我做错了什么和/或误会了? 3)管理嵌套MOC保存和单个模型 - 实体 - 属性 - 更改保存的正确模式是什么?

谢谢!

卡尔

1 个答案:

答案 0 :(得分:3)

您的实施存在许多问题。

  1. 方法diskManagedObjectContextbackgroundManagedObjectContext

    使用全局并发队列,这没有任何意义。如果要使方法线程安全(考虑到实例变量是共享资源),则需要使用专用队列(串行或并发)并使用dispatch_barrier_sync返回一个值,dispatch_barrier_async一个值。

  2. 您的saveChanges方法应该是异步的

    简单通用的实现可能如下所示:

    - (void) saveContextChainWithContext:(NSManagedObjectContext*)context 
                              completion:(void (^)(NSError*error))completion
    {
        [context performBlock:^{
            NSError* error;
            if ([context save:&error]) {
                NSManagedObjectContext* ctx = [context parentContext];
                if (ctx) {
                    dispatch_async(dispatch_get_global(0,0), ^{
                        [self saveContextChainWithContext:ctx completion:completion];
                    });
                }
                else {
                    if (completion) {
                        completion(nil);
                    }
                }
            }
            else {
                if (completion) {
                    completion(error);
                }
            }
        }];
    }
    
  3. 通常,使用方法的同步阻止版本总是容易出现死锁 - 即使Core Data努力尽力避免此类情况。

    因此,尽可能考虑异步 - 并且不会遇到死锁。在您的场景中,使用非阻塞异步样式很容易。