核心数据嵌套的托管对象上下文和频繁的死锁/冻结

时间:2012-08-02 21:59:50

标签: objective-c ios core-data nsfetchedresultscontroller nsmanagedobjectcontext

我的问题几乎与此人描述的问题相同,但未得到解答:

http://www.cocoabuilder.com/archive/cocoa/312683-core-data-nested-managed-object-contexts-and-frequent-deadlocks.html#312683

以下是问题:

我有一个使用NSPrivateQueueConcurrencyType和持久性存储协调器集的父MOC设置,它有一个带有NSMainQueueConcurrencyType的子MOC设置。大多数长期努力工作和保存的想法可以在私有MOC上完成,从而使主线程阻止UI。不幸的是,我似乎遇到了导致死锁的几种情况。

如果子MOC(在主线程上)正在使用NSFetchedResultsController执行提取,则会向父上下文发送-executeFetchRequest:它可以创建死锁。这两个操作都是在performBlock的上下文中完成的:对于它们各自的MOC,虽然文档似乎表明在没有performBlock的情况下在主线程上使用主线程并发类型MOC是正常的。

看起来私有队列正在等待主线程上的子上下文已经锁定的PSC锁。看起来子上下文(在持有PSC锁的情况下)试图将dispatch_sync分配给父上下文,因此它们都在等待彼此。

是PriveQueue - > MainQueue支持配置?似乎大多数人仍然在主线程上拥有父上下文。

主线程如下:

> #0    0x960f6c5e in semaphore_wait_trap ()
> #1    0x04956bb5 in _dispatch_thread_semaphore_wait ()
> #2    0x04955c8f in _dispatch_barrier_sync_f_slow ()
> #3    0x04955dea in dispatch_barrier_sync_f ()
> #4    0x01797de5 in _perform ()
> #5    0x01798547 in -[NSManagedObjectContext(_NestedContextSupport) newValuesForObjectWithID:withContext:error:] ()
> #6    0x0176416b in _PFFaultHandlerLookupRow ()
> #7    0x01763f97 in -[NSFaultHandler fulfillFault:withContext:forIndex:] ()
> #8    0x01763b75 in _PF_FulfillDeferredFault ()
> #9    0x017639f2 in _sharedIMPL_pvfk_core ()
> #10    0x017681a0 in _pvfk_11 ()
> #11    0x0001b322 in -[FBUser sectionName] at /Users/mlink/Code/x/x/FBUser.m:62
> #12    0x011a8813 in _NSGetUsingKeyValueGetter ()
> #13    0x017a0652 in -[NSManagedObject valueForKey:] ()
> #14    0x011ab8d5 in -[NSObject(NSKeyValueCoding) valueForKeyPath:] ()
> #15    0x01851f72 in -[NSFetchedResultsController(PrivateMethods) _sectionNameForObject:] ()
> #16    0x01853af6 in -[NSFetchedResultsController(PrivateMethods) _computeSectionInfo:error:] ()
> #17    0x01850ea6 in -[NSFetchedResultsController performFetch:] ()
> #18    0x0003a4fc in __62-[SYFriendsTableViewController updateFetchedResultsController]_block_invoke_0 ()
> #19    0x01797af3 in developerSubmittedBlockToNSManagedObjectContextPerform ()
> #20    0x049554f0 in _dispatch_main_queue_callback_4CF ()
> #21    0x01b3e833 in __CFRunLoopRun ()
> #22    0x01b3ddb4 in CFRunLoopRunSpecific ()
> #23    0x01b3dccb in CFRunLoopRunInMode ()
> #24    0x023d6879 in GSEventRunModal ()
> #25    0x023d693e in GSEventRun ()
> #26    0x0089aa9b in UIApplicationMain ()
> #27    0x00002656 in main at /Users/mlink/Code/x/x/main.mm:16

私有队列堆栈如下所示:

#0    0x960f8876 in __psynch_mutexwait ()
#1    0x97e9e6af in pthread_mutex_lock ()
#2    0x0172ec22 in -[_PFLock lock] ()
#3    0x0172ebfa in -[NSPersistentStoreCoordinator lock] ()
#4    0x01746a8c in -[NSManagedObjectContext(_NSInternalAdditions) lockObjectStore] ()
#5    0x01745030 in -[NSManagedObjectContext executeFetchRequest:error:] ()
#6    0x0009d49f in -[NSManagedObjectContext(Additions) executeFetchRequest:] at /Users/mlink/Code/objc/C/C/NSManagedObjectContext+Additions.m:44
#7    0x0002177f in +[FBUser usersForFbids:inManagedObjectContext:] at /Users/mlink/Code/x/x/FBUser.m:435
#8    0x00021fc0 in __77+[FBUser updateUserFromGraphValues:inManagedObjectContext:completionHandler:]_block_invoke_0 at /Users/mlink/Code/x/x/FBUser.m:461
#9    0x0180f9f3 in developerSubmittedBlockToNSManagedObjectContextPerform_privateasync ()
#10    0x04954ecf in _dispatch_queue_drain ()
#11    0x04954d28 in _dispatch_queue_invoke ()
#12    0x049544af in _dispatch_worker_thread2 ()
#13    0x97ea1b24 in _pthread_wqthread ()
#14    0x97ea36fe in start_wqthread ()

他也写道:

我开始认为问题出在NSFetchedResultsController上,它始终停留在performFetch:当发生这些死锁时。大多数时候,由于要求它的部分名称,它会因为试图在对象中出错而被卡住。作为测试,我尝试重现FRC的功能并执行executeFetchRequest:然后遍历结果,询问每个对象的部分名称。这不会造成僵局。如果我离开FRC去执行performFetch:在我进行测试后它仍然会死锁。我99%确定FRC与嵌套上下文有同步问题。

问题:有人知道为什么会出现这个问题吗?你知道怎么解决吗?这是一个错误吗?

7 个答案:

答案 0 :(得分:34)

我刚读过this SO posting,其中fabrice truillot de chambrier建议目前不要使用嵌套的上下文。他提到了文章Core Data Growing Pains

从那篇文章:

  

NSFetchedResultsController死锁

     

您永远不希望您的应用程序陷入僵局。同   NSFetchedResultsController和嵌套的上下文,很容易   做。使用上述相同的UIManagedDocument设置,执行   使用时在私有队列上下文中获取请求   NSFetchedResultsController可能与主队列上下文有关   僵局。如果你几乎在同一时间开始它   几乎100%的一致性。 NSFetchedResultsController可能是   获得一个它不应该的锁。据报道这是固定的   即将推出的iOS版本。

     雷达:// 11861499修正了即将发布的版本

这似乎准确描述了您的问题。

答案 1 :(得分:2)

对于我的iOS 6应用程序,我具有与OP相同的并发设置 - 在主线程上使用私有队列和子MOC的父MOC。我还有一个NSFetchedResultsController,它使用子MOC来更新UITableViewController。这两个MOC都在AppDelegate中初始化,并将在整个应用程序中使用。 AppDelegate有两个方法, savePrivateThreadMOCToCoreData saveMainThreadMOCToCoreData ,以保持对CD的更改。在启动时,我按如下方式在私有队列上调度coredata初始化程序。我们的想法是立即将用户放入表格视图,并允许初始化程序在后台更新核心数据。

    dispatch_async(private_queue,^{
        [CoreDataInitializer initialize];
    });

最初,当 savePrivateThreadMOCToCoreData 在-performBlock中执行保存时,我看到上面链接的“Core Data Growing Pains”中描述的psynch_mutex死锁相同。如果我在保存过程中尝试将数据读入TableVC,我也看到了崩溃。

    Collection <__NSCFSet: 0x7d8ea90> was mutated while being enumerated.

为了克服这些问题,我转而使用-performBlockAndWait进行保存。我不再看到死锁和崩溃但是让UI等待保存感觉不对。最后,我删除了对-performBlock *的所有调用并使用了普通的[privateMOC save:&amp; error],就这样,我的所有问题都消失了。获取的结果控制器干净地读取部分保存的数据并更新表,不再有死锁或“在枚举时突变”错误。

我怀疑 -performBlock *应该被其他线程使用,这些线程没有创建有问题的MOC,以请求对其进行操作。由于我的私有和主要线程MOC都属于app委托,因此私有MOC上的保存不应使用 -performBlock *。

虽然我的构建环境是iOS 6,但我的基本部署目标SDK iOS 5.0可能是相关的。似乎其他人不再在iOS 6中看到这个问题了。

答案 2 :(得分:0)

它发生在我身上,因为父母设置了NSMainQueueConcurencyType

要解决这个问题,我将mainQueue的managedobjectcontext设为子节点。我每次想要加载东西时都调用reset来确保mainQueue上的数据与父代相同。通常不是。

答案 3 :(得分:0)

我也遇到了与 developerSubmittedBlockToNSManagedObjectContextPerform 相关的崩溃。

在我的情况下,请考虑以下方法调用模式:

[privatecontext performBlock:^{
    A(CDManager.privatecontext);
}];

其中: A(CDManager.privateContext)调用B() B()调用C() C()调用D()

和: 方法A()和方法C()包含一些核心数据操作。 A()已经知道要处理哪个上下文,但是A()并没有通知B()有关上下文的内容,所以C()也没有关于要处理哪个上下文的任何信息,所以C()可以工作默认上下文(主要)。这会导致由于数据不一致而导致崩溃。

修正: 所有用于db操作的方法都使用它们要处理的上下文进行参数化,除了D(),因为它不需要在db操作上工作,如:

A(上下文)调用B(上下文) B(上下文)调用C(上下文) C(上下文)调用D()

答案 4 :(得分:0)

我解决了同时从两个线程获取的死锁导致的完全相同的问题(BG执行触发fetchRequest,MAIN执行了NSFRC的获取)。解决方案是为长时间运行的同步操作创建新的上下文。它没有父上下文,它具有并发类型NSPrivateQueueConcurrencyType,并且它直接与公共PSC链接。在后台在此上下文中完成所有长时间运行的工作之后,我将其保存并使用mergeChangesFromContextDidSaveNotification例程将其与rest parallel contexts stack合并。

在Magical Record 3中实现了一个很好的解决方案。点击此处查看更多信息:https://stackoverflow.com/a/25060126/1885326

答案 5 :(得分:-1)

  

这个想法是大部分长时间的努力和保存都可以在私人MOC上完成

你是如何实现这个想法的?你使用这样的东西:

- (void)doSomethingWithDocument:(UIManagedDocument *)document
{
    NSManagedObjectContext *parent = document.managedObjectContext.parentContext;
        [parent performBlock:^{
            /* 
               Long and expensive tasks.. 
               execute fetch request on parent context
               download from remote server               
            */
            // save document
        }];
}

我上面做了并且也遇到了僵局。然后我尝试不要触摸父上下文的后备队列。相反,我使用简单的GCD来下载内容,并在子上下文中(在主队列上)操作核心数据。它工作正常。通过这种方式,父上下文似乎无用..但至少它不会导致死锁..

- (void)doSomethingWithDocument:(UIManagedDocument *)document
{
    dispatch_queue_t fetchQ = dispatch_queue_create("Flickr fetcher", NULL);
    dispatch_async(fetchQ, ^{
        // download from remote server        
        // perform in the NSMOC's safe thread (main thread)
        [document.managedObjectContext performBlock:^{ 
            // execute fetch request on parent context
            // save document  
        }];
    });
    dispatch_release(fetchQ);
}

答案 6 :(得分:-1)

我只是想加入并完全同意避免嵌套的上下文。我一直在iOS 7中使用嵌套上下文(主队列子级和私有队列父级)和NSFetchedResultsControllers,并且无法解决死锁问题。我转而使用独立的MOC并保存通知,问题就消失了。

如果有人需要有关如何更改代码的快速指南,则此页面包含已准备就绪的代码(只需忽略嵌套的上下文建议):

http://www.cocoanetics.com/2012/07/multi-context-coredata/