Core Data nested managed object contexts and frequent deadlocks / freezes

纵然是瞬间 提交于 2019-11-27 17:43:21
Martin R

I just read this SO posting where fabrice truillot de chambrier recommends not to use nested contexts at present. He gives a reference to the article Core Data Growing Pains .

From that article:

NSFetchedResultsController deadlocks

You never want your application to deadlock. With NSFetchedResultsController and nested contexts, it’s pretty easy to do. Using the same UIManagedDocument setup described above, executing fetch requests in the private queue context while using NSFetchedResultsController with the main queue context will likely deadlock. If you start both at about the same time it happens with almost 100% consistency. NSFetchedResultsController is probably acquiring a lock that it shouldn’t be. This has been reported as fixed for an upcoming release of iOS.

radar://11861499 fixed in an upcoming release

This seems to describe exactly your issue.

For my iOS 6 app, I have the same concurrency setup as the OP - a parent MOC using a private queue and a child MOC on the main thread. I also have an NSFetchedResultsController which uses the child MOC to update a UITableViewController. Both these MOCs are initialized in the AppDelegate and are to be used across the app. The AppDelegate has two methods, savePrivateThreadMOCToCoreData and saveMainThreadMOCToCoreData, to persist changes to CD. At launch, I dispatch a coredata initializer on a private queue as follows. The idea is to immediately drop the user into the table view and allow the initializer to update core data in the background.

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

Initially, when savePrivateThreadMOCToCoreData was doing saves in a -performBlock, I was seeing the same psynch_mutex deadlocks described in "Core Data Growing Pains" linked above. I also saw crashes if I tried reading data into the TableVC while the save was in progress.

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

To overcome these, I switched to doing saves using -performBlockAndWait. I stopped seeing deadlocks and crashes but it didn't feel right to make the UI wait for saves. Finally, I removed all calls to -performBlock* and used a plain vanilla [privateMOC save:&error] and just like that, all my issues disappeared. The fetched results controller reads partially saved data cleanly and updates the table, no more deadlocks or "mutated while being enumerated" errors.

I suspect -performBlock* is supposed to be used by other threads, ones which didn't create the MOC in question, to request operations on it. Since both my private and main thread MOCs belong to the app delegate, saves on the private MOC should not use -performBlock*.

It's probably relevant that though my build environment is iOS 6, my base deployment target SDK iOS 5.0. Seems others aren't seeing this issue with iOS 6 any more.

It happens to me because the parents are set up with NSMainQueueConcurencyType

To solve that I make the managedobjectcontext for mainQueue to be a child. I called reset everytime I want to load stuff to ensure that data on mainQueue is the same with the parent. It often isn't.

I too got a crash related to developerSubmittedBlockToNSManagedObjectContextPerform.

In my case, consider the following method calling pattern:

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

where: A(CDManager.privateContext) calls B() B() calls C() C() calls D()

and: method A() and method C() contains some Core Data operations. A() already have the knowledge to which context to work on, but A() doesnt inform B() about the context and so C() too doesnt have any info about on which context to work on, so C() works on default context (main). and this causes the crash due to data inconsistently in db.

fix: all the methods which are to work on db operations are parametrized with the context they are to work on, except D() since it need not work on db operation, like:

A(context) calls B(context) B(context) calls C(context) C(context) calls D()

kas-kad

I solved the exact same problem with deadlocks caused by simultaneous fetching from two threads (BG executed fired fetchRequest, MAIN one performed fetch of NSFRC). The solution is to create a new context for the long running sync operation. It has no parent context, it has concurrency type NSPrivateQueueConcurrencyType and it directly linked with a common PSC. After all long running work is done within this context in background, I save it and merge it with rest parallel contexts stack by using mergeChangesFromContextDidSaveNotification routine.

A great solution is implemented in Magical Record 3. See more info here: https://stackoverflow.com/a/25060126/1885326.

The idea being most of the long hard work and saves can be done on the private MOC

How do you implement that idea? Do you use something like this:

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

I did above and got deadlock too. Then I tried not to touch the backing queue of parent context. Instead, I use plain and simple GCD to do the downloading stuff, and manipulate core data in child context (on main queue). It works fine. In this way parent context seems useless.. But at least it doesn't cause deadlock..

- (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);
}

I just wanted to chime in and agree completely with avoiding nested contexts. I've been working in iOS 7 with nested contexts (main queue child and private queue parent) and NSFetchedResultsControllers and had an impossible to fix deadlock issue. I switched over to using independent MOCs and save notifications and the issue disappeared.

If anyone needs a quick guide for how to change their code over, this page has code that's ready to go (just ignore the nested context recommendation):

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!