在iOS 7上使用Private Queue Deadlocks Parent的子级上下文执行块locking和等待

我有两个名为importContextchildContext NSManagedObjectContextchildContextchildContext的子importContext ,它们都是NSPrivateQueueConcurrencyType

为了避免主线程,我在importContext的队列上做了一堆工作。 这个工作涉及到大量的提取和保存,所以将整个事件封装到importContextperformBlockAndWait:中是很importContext (因为在performBlockAndWait之后的代码依赖于它的结果, 所以需要通过同步操作)。

在这个工作的某个时候,我可能需要从JSON结果创build新的托pipe对象。 这些JSON值可能是无效的,并且我的validation失败,所以在创build对象之后,如果它们不好,我需要将它们排除。 这是childContext进来的地方。我插入我的新对象,如果它的JSON属性最终没有意义,我抛弃了childContext

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

看起来好像当我运行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!!!! }]; } .... } }]; } 

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

更新1

回覆。 马库斯的问题:

  1. updateItems:从操作队列中的NSInvocationOperation被调用,所以它不在主队列中。

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

     (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 

我上面显示的代码是一个简化的版本。 我创build一个新的子上下文的部分是在一个名为DBManagedObject的类中。 下面是整个堆栈的截图:

在这里输入图像说明

更新2 – 解释DBManagedObject

DBManagedObject是我所有核心数据类的基类。 它基本上处理来自JSONparsing字典的转换。 它有3个主要方法: +createWithAttributes:inManagedObjectContext:error: ,- -updateWithAttributes:error:attributes

  • +createWithAttributes:inManagedObjectContext:error:创build提供的托pipe对象上下文的子上下文,在子上下文中插入一个新对象,并在该对象上调用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; 
  • updateWithAttributes:error:在JSON键与我在数据模型中用作实体属性的键之间进行了翻译。 (即'first_name'变成'firstName')。 它还会根据需要格式化JSON值(datestring变成NSDate )。 它也设置关系。

谁在调用-updateItems: 如果在主队列中出现这个问题,那么您有一个问题,因为您正在阻止它。

假设情况并非如此,你能分享从Xcode线程堆栈显示截止date? 特别是队列扩展和主队列扩展?

一旦我看清了堆栈,我会更新我的答案。

冷静是正确的,你没有正确地插入到孩子。 该子级MOC上的任何活动都应位于-performBlock:-performBlockAndWait: 。 我将展开-performBlockAndWait:覆盖整个对象的创build和决定,而不仅仅是保存。

更新1

什么是-createWithAttributes:inManagedObjectContext:error:做什么? 似乎这种方法正在做一些不恰当的事情。 也许试图强制永久ID或什么的?

更新2

怀疑,你的-createWithAttributes:inManagedObjectContext:error:是你的问题。 当您调用-objectWithID:您正在导致一个抓取在NSPersistentStoreCoordinator一路向下,这反过来会导致locking。

此外,这种方法没有任何帮助。 创build一个上下文对于创build一个对象是完全没有任何价值的,然后立即在另一个上下文中获取对象。 所有的伤害,不好。 完全删除它,只是在实际上要使用它的上下文中创build对象。 从正在使用的上下文中保存或抛弃它。

不要聪明。

从看你的代码我看你有2 [childContext performBlockAndWait:^ {是嵌套的。 删除其中的一个应该清除你的问题在ios7。 代码已经在该线程中运行,您不必再次执行此操作。

总是检查是否有相同的上下文嵌套performBlocks。 这导致我的应用程序在ios7之前陷入僵局,并在ios8中工作

检查的方法是当你看到死锁时,按下debugging器中的暂停,并查看所有线程正在运行的块。 看看这个特定的代码,并检查嵌套块。