Базовые контексты вложенных управляемых объектов и частые тупики / зависания
У меня есть проблема, которая почти идентична проблеме, описанной здесь этим человеком, но она не получила ответа:
Вот проблема:
У меня есть родительская настройка 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 и сохранял уведомления, и проблема исчезла.
Если кому-то нужно краткое руководство о том, как изменить свой код, на этой странице есть готовый код (просто игнорируйте рекомендации по вложенному контексту):
Идея, заключающаяся в том, что большую часть долгой и трудной работы можно проделать на частной 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);
}