performBlockAndWait在iOS 7上使用专用队列死锁的子上下文

Shi*_*ami 7 concurrency core-data nsmanagedobjectcontext ios

我有两个NSManagedObjectContext名字importContextchildContext.childContext是他们的孩子importContext,他们俩都是NSPrivateQueueConcurrencyType.

为了避开主线程,我正在对importContext队列进行大量工作.这项工作涉及大量取的并保存,所以它的方便来包装整个事情里面performBlockAndWait:importContext(它不会被同步操作需要,因为我以后有代码performBlockAndWait依赖于它的结果).

在这项工作中的某些时候,我可能需要从JSON结果创建新的托管对象.这些JSON值可能无效并且我的验证失败,因此在创建对象后,如果它们不好,我需要能够抛弃它们.这就是childContext进来的地方.我将新对象插入其中,如果它的JSON属性最终没有意义,我就放弃了childContext.

当我需要保存时,问题就来了childContext.我希望它有自己的私有队列,与父队列分开.但是,这会导致iOS 7(不是iOS 8)上的死锁.当我在iOS 8模拟器和设备上运行相同的代码时,childContext它会在单独的线程上创建自己的队列并正确进行保存.

看起来当我运行iOS 7时,它childContext正在尝试save:在父队列中进行,但是父进程正在等待其子进程导致死锁.在iOS 8中,这不会发生.有谁知道为什么?

这是简化的代码:

   -(NSManagedObjectContext *)importContext
   {
       NSManagedObjectContext* moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
       moc.persistentStoreCoordinator = [self storeCoordinator];
       return moc;
   }

   -(void)updateItems:(NSArray*)ItemDescriptions
   {
      [self.importContext performBlockAndWait:^{
           //get info and update
           ...
           ...

       if(needToCreateNewItem){
          NSManagedObjectContext* childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
          childContext.parentContext = self.importedContext;

          //Insert and create new item 
          ...
          [childContext performBlockAndWait:^{
              id newObject = [NSEntityDescription insertNewObjectForEntityForName:[self entityName]
                                                 inManagedObjectContext:childContext];
          }];
          ...

          // Do something with this object

          if([newObject isReadyToSave])
              __block NSError* e = nil;
              __block BOOL saveSucceeded = NO;

              [childContext performBlockAndWait:^{
                 saveSucceeded = [childContext save:&e]; // DEADLOCK ON iOS 7!!!!
              }];
          }
          ....

       }
  }];  
}
Run Code Online (Sandbox Code Playgroud)

一个简单的解决方法是将工作保持在一个单独的调度队列(而不是importContext队列),但我问这个问题的原因是因为我想了解发生这种情况的根本原因.我认为孩子的保存应该只发生在自己的队列中.

更新1

回覆.马库斯的问题:

  1. updateItems:从一个NSInvocationOperation操作队列中调用,因此它离开主队列.

  2. 在iOS 7上,我可以随时暂停应用程序并查看堆栈,托管对象上下文的队列将被解锁:

    (lldb) bt
    
    * thread #7: tid = 0xed07, 0x38546aa8 libsystem_kernel.dylib`semaphore_wait_trap + 8, queue = 'NSManagedObjectContext Queue'
    frame #0: 0x38546aa8 libsystem_kernel.dylib`semaphore_wait_trap + 8
    frame #1: 0x385bbbac libsystem_platform.dylib`_os_semaphore_wait + 12
    frame #2: 0x3848461a libdispatch.dylib`_dispatch_barrier_sync_f_slow + 138
    frame #3: 0x2d4f3df2 CoreData`_perform + 102
    frame #4: 0x2d4fe1ac CoreData`-[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:] + 240
    frame #5: 0x2d492f42 CoreData`-[NSManagedObjectContext save:] + 826
      * frame #6: 0x000c1c96 DBDevApp`__69+[DBManagedObject createWithAttributes:inManagedObjectContext:error:]_block_invoke77(.block_descriptor=<unavailable>) + 118 at DBManagedObject.m:117
    frame #7: 0x2d4f6934 CoreData`developerSubmittedBlockToNSManagedObjectContextPerform + 88
    frame #8: 0x3847e81e libdispatch.dylib`_dispatch_client_callout + 22
    frame #9: 0x384847ca libdispatch.dylib`_dispatch_barrier_sync_f_invoke + 26
    frame #10: 0x2d4f6a72 CoreData`-[NSManagedObjectContext performBlockAndWait:] + 106
    frame #11: 0x000c1916 DBDevApp`+[DBManagedObject createWithAttributes:inManagedObjectContext:error:](self=0x005c1790, _cmd=0x0054a033, attributes=0x188e    context=0x17500800, error=0x02e68ae8) + 658 at DBManagedObject.m:116
    frame #12: 0x000fe138 DBDevApp`-[DBAPIController createOrUpdateItems:withIDs:IDKeys:ofClass:amongExistingItems:withFindByIDPredicate:](self=0x17775de0, _cmd=0x0054de   newItemDescriptions=0x188eada0, itemIDs=0x18849580, idKey=0x0058e290, class=0x005c1790, existingItems=0x1756b560, findByID=0x18849c80) + 2472 at DBAPIController.m:972
    frame #13: 0x00100ca0 DBDevApp`__39-[DBAPIController updatePatientGroups:]_block_invoke(.block_descriptor=0x02e68ce0) + 476 at DBAPIController.m:1198
    frame #14: 0x2d4f6934 CoreData`developerSubmittedBlockToNSManagedObjectContextPerform   
    frame #15: 0x3847e81e libdispatch.dylib`_dispatch_client_callout + 22
    frame #16: 0x384847ca libdispatch.dylib`_dispatch_barrier_sync_f_invoke + 26
    frame #17: 0x2d4f6a72 CoreData`-[NSManagedObjectContext performBlockAndWait:] + 106
    frame #18: 0x00100a96 DBDevApp`-[DBAPIController updatePatientGroups:](self=0x17775de0, _cmd=0x0054dfcd, groupsArray=0x188eada0) + 214 at DBAPIController.m:1191
    frame #19: 0x2d721584 CoreFoundation`__invoking___ + 68
    frame #20: 0x2d66c0da CoreFoundation`-[NSInvocation invoke] + 282
    frame #21: 0x2e0f3d2c Foundation`-[NSInvocationOperation main] + 112
    frame #22: 0x2e0515aa Foundation`-[__NSOperationInternal _start:] + 770
    frame #23: 0x2e0f576c Foundation`__NSOQSchedule_f + 60
    frame #24: 0x38484f10 libdispatch.dylib`_dispatch_queue_drain$VARIANT$mp + 488
    frame #25: 0x38484c96 libdispatch.dylib`_dispatch_queue_invoke$VARIANT$mp + 42
    frame #26: 0x38485a44 libdispatch.dylib`_dispatch_root_queue_drain + 76
    frame #27: 0x38485d28 libdispatch.dylib`_dispatch_worker_thread2 + 56
    frame #28: 0x385c0bd2 libsystem_pthread.dylib`_pthread_wqthread + 298
    
    Run Code Online (Sandbox Code Playgroud)

我上面展示的代码是一个简化版本.我创建一个新子上下文的部分位于一个名为的类中DBManagedObject.这是整个堆栈的屏幕截图:

在此输入图像描述

更新2 - 解释 DBManagedObject

DBManagedObject是我所有核心数据类的基类.它基本上处理与JSON解析的字典之间的转换.它有3种主要方法:+createWithAttributes:inManagedObjectContext:error:,-updateWithAttributes:error:,和attributes.

  • +createWithAttributes:inManagedObjectContext:error::创建提供的托管对象上下文的子上下文,在子上下文中插入新对象并调用updateWithAttributes:error:该对象.如果更新成功(即,我们想要在此对象上设置的所有值都有意义),它将保存子上下文,获取对作为参数作为参数的MOC中的新对象的引用,并返回该引用:

    NSManagedObjectContext* childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    childContext.parentContext = context;
    __block id newObject;
    [childContext performBlockAndWait:^{
        newObject = [NSEntityDescription insertNewObjectForEntityForName:[self entityName] inManagedObjectContext:childContext];
    }];
    
    if ([newObject updateWithAttributes:attributes error:error])
    {
        NSError* e = nil;
        if ([childContext save:&e])
        {
            id parentContextObject = [context objectWithID:[(NSManagedObject*)newObject objectID]];
            return parentContextObject;
        }
        else
        {
             if (error != NULL) {
                *error = e;
            }
            return nil;
        }
    }
    else
        return nil;
    
    Run Code Online (Sandbox Code Playgroud)
  • updateWithAttributes:error::将JSON键之间的键转换为我在数据模型中使用的键作为实体属性的繁重工作.(即'first_name'变为'firstName').如果需要,它还格式化JSON值(日期字符串变为NSDates).它还建立了关系.

Mar*_*rra 0

是谁打来的-updateItems:?如果它进入主队列,那么您就会遇到问题,因为您正在阻止它。

假设情况并非如此,您能否分享 Xcode 中显示截止日期的线程堆栈?特别是队列扩展和主队列扩展?

一旦我仔细查看了该堆栈,我就会更新我的答案。

Quellish 是正确的,您没有正确插入孩子体内。该子 MOC 上的任何活动都应位于-performBlock:或内-performBlockAndWait:。我将扩展-performBlockAndWait:来涵盖对象的整个创建和决策,而不仅仅是保存。

更新1

有什么-createWithAttributes:inManagedObjectContext:error:作用?看来这个方法做了一些不恰当的事情。也许试图强制使用永久身份证或其他什么?

更新2

正如怀疑的那样,你的-createWithAttributes:inManagedObjectContext:error:问题就是你的。当您调用时,-objectWithID:您会导致 fetch 一直向下触发,从而NSPersistentStoreCoordinator导致锁定。

此外,这种方法没有任何帮助。仅仅为了创建一个对象然后立即在另一个上下文中获取该对象而创建一个上下文绝对没有任何价值。皆是害处,无益。完全删除它,然后在您实际要使用它的上下文中创建对象。将其保存或从正在使用的上下文中丢弃。

别自作聪明。