Базовые контексты вложенных управляемых объектов и частые тупики / зависания

У меня есть проблема, которая почти идентична проблеме, описанной здесь этим человеком, но она не получила ответа:

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

Вот проблема:

У меня есть родительская настройка MOC с NSPrivateQueueConcurrencyType и постоянный набор координаторов хранилища, у него есть дочерняя настройка MOC с NSMainQueueConcurrencyType. Идея, заключающаяся в том, что большую часть долгой и трудоемкой работы можно сохранить на частном MOC, освобождает основной поток от блокировки пользовательского интерфейса. К сожалению, я, кажется, сталкиваюсь с парой ситуаций, которые вызывают тупики.

Если дочерний MOC (в главном потоке) выполняет выборку с помощью NSFetchedResultsController, родительскому контексту отправляется -executeFetchRequest: он может создать взаимоблокировку. Обе операции выполняются в контексте executeBlock: для их соответствующих MOC, хотя документы, по-видимому, указывают, что использование типа MOC с параллелизмом основного потока в основном потоке без executeBlock: нормально.

Похоже, что частная очередь ожидает блокировки PSC, которую дочерний контекст в основном потоке уже заблокировал. Похоже, что дочерний контекст (удерживая блокировку PSC) пытается диспетчеризировать_синхронизацию с родительским контекстом, и поэтому они оба ждут друг друга.

Является ли 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, который всегда застревает в executeFetch: когда происходят эти взаимоблокировки. Большую часть времени он застревает, пытаясь вызвать ошибку в объекте в результате запроса его имени раздела. В качестве теста я попытался воспроизвести то, что делает FRC, и выполнил executeFetchRequest: а затем перебрал результаты, запрашивая у каждого объекта свое имя раздела. И это не вызывает тупик. Если я оставлю FRC, чтобы выполнить executeFetch: после того, как я выполню свой тест, он все равно будет там тупиковым. Я на 99% уверен, что у FRC есть проблема синхронизации с вложенными контекстами.

Вопрос: Кто-нибудь знает, почему возникает эта проблема? Ты знаешь как это решить? Это ошибка?

7 ответов

Решение

Я только что прочитал эту публикацию SO, где Fabrice Truillot de Chambrier рекомендует не использовать вложенные контексты в настоящее время. Он дает ссылку на статью Core Data Growing Pains.

Из этой статьи:

NSFetchedResultsController взаимоблокировки

Вы никогда не хотите, чтобы ваше приложение зашло в тупик. С NSFetchedResultsController и вложенными контекстами это довольно легко сделать. Используя ту же настройку UIManagedDocument, которая описана выше, выполнение запросов на выборку в контексте частной очереди при использовании NSFetchedResultsController с контекстом основной очереди, вероятно, приведет к взаимоблокировке. Если вы начинаете оба одновременно, это происходит с почти 100% согласованностью. NSFetchedResultsController, вероятно, получает блокировку, которой не должно быть. Это было исправлено в предстоящем выпуске iOS.

радар://11861499 исправлено в следующем выпуске

Кажется, это точно описывает вашу проблему.

Для моего приложения для iOS 6 у меня есть та же настройка параллелизма, что и у OP - родительский MOC, использующий личную очередь и дочерний MOC в основном потоке. У меня также есть NSFetchedResultsController, который использует дочерний MOC для обновления UITableViewController. Оба эти MOC инициализируются в AppDelegate и должны использоваться во всем приложении. AppDelegate имеет два метода, savePrivateThreadMOCToCoreData и saveMainThreadMOCToCoreData, чтобы сохранить изменения на CD. При запуске я отправляю инициализатор coredata в личную очередь следующим образом. Идея состоит в том, чтобы немедленно поместить пользователя в табличное представление и позволить инициализатору обновлять данные ядра в фоновом режиме.

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

Первоначально, когда savePrivateThreadMOCToCoreData делала сохранения в -performBlock, я видел те же тупики psynch_mutex, описанные в " Боли роста основных данных", связанные выше. Я также видел сбои при попытке чтения данных в TableVC во время сохранения.

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

Чтобы преодолеть это, я переключился на сохранение с помощью -performBlockAndWait. Я перестал видеть тупики и сбои, но было неудобно заставлять пользовательский интерфейс ждать спасений. Наконец, я удалил все вызовы -performBlock* и использовал простой ваниль [privateMOC save:&error], и все мои проблемы исчезли. Контроллер полученных результатов корректно считывает частично сохраненные данные и обновляет таблицу, больше не возникает взаимоблокировок или ошибок, изменяемых при перечислении.

Я подозреваю, что -performBlock* должен использоваться другими потоками, которые не создавали MOC, для запроса операций над ним. Поскольку и МОК моего частного, и основного потока принадлежат делегату приложения, при сохранении в закрытом МОС не следует использовать -performBlock*.

Вероятно, уместно, что, хотя моя среда сборки - iOS 6, моя основная цель развертывания - SDK iOS 5.0. Кажется, другие больше не видят этой проблемы с iOS 6.

Это происходит со мной, потому что родители настроены с NSMainQueueConcurencyType

Чтобы решить это, я делаю управляемый объектный текст для mainQueue дочерним. Я вызывал reset каждый раз, когда я хочу загрузить материал, чтобы убедиться, что данные в mainQueue совпадают с данными родителя. Это часто не так.

Я решил ту же проблему с взаимоблокировками, вызванными одновременной выборкой из двух потоков (BG выполнил запрос fetchRequest, MAIN - один выбор NSFRC). Решение состоит в том, чтобы создать новый контекст для длительной операции синхронизации. У него нет родительского контекста, он имеет тип параллелизма NSPrivateQueueConcurrencyType и это напрямую связано с общим PSC. После того, как вся длительная работа выполняется в этом контексте в фоновом режиме, я сохраняю ее и объединяю со стеком остальных параллельных контекстов, используя mergeChangesFromContextDidSaveNotification рутина.

В Magical Record 3 реализовано отличное решение. Подробнее см. Здесь: /questions/32368492/nsfetchedresultscontroller-predstavlenie-tablitsyi-fida-v-to-vremya-kak-fonovoe-obnovlenie-togo-zhe-samogo-postoyannogo-hranilischa-vyizyivaet-tupik/32368506#32368506.

Я тоже получил сбой, связанный с developerSubmittedBlockToNSManagedObjectContextPerform.

В моем случае рассмотрим следующий шаблон вызова метода:

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

где: A(CDManager.privateContext) вызывает B() B() вызывает C () C () вызывает D()

и: метод A() и метод C () содержат некоторые операции с базовыми данными. A() уже знает, с каким контекстом работать, но A() не сообщает B () о контексте, и поэтому C () тоже не имеет никакой информации о том, над каким контекстом работать, поэтому C () работает контекст по умолчанию (основной). и это вызывает сбой из-за непоследовательных данных в БД.

исправлено: все методы, которые должны работать с операциями БД, параметризованы с контекстом, с которым они должны работать, кроме D(), так как он не должен работать с операцией БД, например:

A (контекст) вызывает B (контекст) B (контекст) вызывает C (контекст) C (контекст) вызывает D()

Я просто хотел присоединиться и полностью согласиться с тем, чтобы избегать вложенных контекстов. Я работал в iOS 7 с вложенными контекстами (основной дочерний элемент очереди и родительский элемент частной очереди) и NSFetchedResultsControllers и не мог решить проблему взаимоблокировки. Я переключился на использование независимых MOC и сохранял уведомления, и проблема исчезла.

Если кому-то нужно краткое руководство о том, как изменить свой код, на этой странице есть готовый код (просто игнорируйте рекомендации по вложенному контексту):

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

Идея, заключающаяся в том, что большую часть долгой и трудной работы можно проделать на частной 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);
}
Другие вопросы по тегам