2011-01-15 2 views
5

Я прочитал главу Маркуса Зарры о многопоточности в своей книге «Основные данные» и довольно внимательно посмотрел на его образец кода. Но его код и другие, которые я нашел в другом месте, похоже, сосредоточены в фоновых процессах, которые не должны знать друг о друге. Эти примеры хороши для импорта древовидной структуры, но они не затрагивают импорт более общей (сложной) структуры, такой как направленный ациклический граф.Основные данные и многопоточность

В моем случае я пытаюсь проанализировать иерархию классов C++ и хотел бы использовать как можно больше NSOperations. Я хотел бы создать экземпляр NSManagedObject для каждого встреченного класса, и я хотел бы объединить различные NSManagedObjectContext, когда он будет сохранен.

В стороне: я могу заставить все работать с одним NSOperation, которое выполняет итерацию файлов и анализирует их по одному. В этой реализации метод -mergeChanges: подход, который вызывает -mergeChangesFromContextDidSaveNotification: в MOC основного потока работает хорошо.

Но, в идеале, я бы хотел, чтобы один NSPperation перебирал исходные файлы и запускал NSOperations для анализа каждого файла. Я попробовал несколько подходов - но, похоже, не все правильно. Наиболее перспективным было то, чтобы каждый NSOperation наблюдал NSManagedObjectContextDidSaveNotification. С -mergeChanges: глядя, как это:

- (void) mergeChanges:(NSNotification *)notification 
{ 
// If locally originated, then trigger main thread to merge. 
if ([notification object] == [self managedObjectContext]) 
    { 
    AppDelegate *appDelegate = (AppDelegate*)[[NSApplication sharedApplication] delegate]; 
    NSManagedObjectContext *mainContext = [appDelegate managedObjectContext]; 

    // Merge changes into the main context on the main thread 
    [mainContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) 
     withObject:notification 
     waitUntilDone:YES]; 
    return; 
    } 
    // If not locally originated, then flag need to merge with in this NSOperation's thread. 
[self setNeedsToMerge:YES]; 
[self setMergeNotification:notification]; 
} 

По существу, разбор NSOperation Мейн() проверяется Ивар 'needsToMerge' периодически. Если это правда, то -mergeChangesFromContextDidSaveNotification: вызывается в локальном MOC с кешированными NSNotifications. И затем requireToMerge был сброшен. Если уведомление возникло локально, то основной поток сказал, чтобы выполнить -mergeChangesFromContextDidSaveNotification: на своем MOC.

Я уверен, что есть хорошая причина, почему это не работает, и почему я получаю это:

предупреждение: Отмена вызова - ObjC код на стек текущего потока делает это небезопасно.

Я также попытался использовать блокировку NSPeristentStoreCoordinator для контроля доступа - но это проблематично, если оно проводится во время вызова -сохраняйте NSManagedObjectContext в: метод, потому что -save: уведомит заинтересованных наблюдателей сохранить событие и -mergeChangesFromContextDidSaveNotification: похоже, блокирует попытку приобретения блокировки PSC.

Просто кажется, что это должно быть намного проще.

ответ

2

Я думаю, что я мастерил ту же проблему, а вот как я это решаю:

Создание пользовательского NSOperation класса, где вы определяете:

NSMutableArray * changeNotifications; 
NSLock * changeNotificationsLock; 
NSManagedObjectContext * localManagedObjectContext; 

В вашем NSOperation основного метода перед сохранением контекста первого применения все запрашиваемые изменения:

[self.changeNotificationsLock lock]; 
for(NSNotification * change in self.changeNotifications){ 
    [self.localManagedObjectContext mergeChangesFromContextDidSaveNotification:change]; 
} 
if([self.changeNotifications count] >0){ 
    [self.changeNotifications removeAllObjects]; 
} 
[self.changeNotificationsLock unlock]; 

NSError *error = nil; 
[self.localManagedObjectContext save:&error] 

Обратите внимание, что я использовал замок, это потому, что NSMutableArray не поточно, и я хочу, чтобы безопасно получить доступ к changeNotifications. changeNotifications - это массив, в котором сохраняются все изменения, которые необходимо применить перед сохранением контекста.

И вот ваш метод слияния, измененный так, что все изменения, которые необходимо объединить с помощью NSOperation, объединяются с использованием правильной нити. Обратите внимание, что эти методы называют другими потоками, чем ваш NSOperation один, поэтому вам нужно заблокировать доступ к self.changeNotifications

- (void) mergeChanges:(NSNotification *)notification 
{ 
// If not locally originated, then add notification into change notification array 
// this notification will be treated by the NSOperation thread when needed. 
if ([notification object] != self.localManagedObjectContext) 
    { 
    [self.changeNotificationsLock lock]; 
    [self.changeNotifications addObject:notification]; 
    [self.changeNotificationsLock unlock]; 
    } 

//Here you may want to trigger the main thread to update the main context  

} 

Надежда этой помощи! Этот метод не является устойчивым на 100%, могут быть случаи, когда уведомление об изменении может появиться слишком поздно. В этом случае метод сохранения контекста вернет ошибку, и вам нужно перезагрузить NSManagedObject и снова сохранить его. Если вам нужна дополнительная информация, пожалуйста, дайте мне знать.

+0

это приятное решение. Для меня, вероятно, мне нужно будет применить changeNotifications до поиска объекта в дополнение к до сохранения - но это было бы легко. Насколько я понимаю, все потоки (кроме основного потока) будут экземплярами этого подкласса. Это так? – westsider

+0

@ Всем всем NSOperation, которые должны объединить контекст, должен быть подкласс этого. В основном я предлагаю создать класс NSOperationManagedOobjectContextAware, который имеет метод mergeChanges и 3 свойства changeNotifications, changeNotificationsLock, localManagedObjectContext –

0

Это два мира в код работает теперь правильно в моем приложении:

- (void)mergeChanges:(NSNotification *)notification; 
{ 
//AppDelegate *appDelegate = [[NSApplication sharedApplication] delegate]; 
NSManagedObjectContext *mainContext = [appDelegate managedObjectContext]; 

// Merge changes into the main context on the main thread 
[mainContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) 
           withObject:notification 
          waitUntilDone:YES]; 
} 

-(void) main { 
// Register context with the notification center 
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 
[nc addObserver:self 
     selector:@selector(mergeChanges:) 
      name:NSManagedObjectContextDidSaveNotification 
     object:managedObjectContext]; 

Конечно, managedObjectContext означают:

managedObjectContext = [[NSManagedObjectContext alloc] init]; 
[managedObjectContext setPersistentStoreCoordinator:[appDelegate persistentStoreCoordinator]]; 
[managedObjectContext setUndoManager:nil]; 

Будьте осторожны, если и нужно, чтобы удалить что-нибудь, прежде чем у сделать обновления от основной MOC. У меня сумасшедшее время и много трудно отлаживать ошибки, в то время как я понимаю, что я не могу использовать moc из основного потока, а где-то обрабатывать другие изменения с тем же контентом.

+0

Я не понимаю, как это будет работать для решения моей проблемы. Похоже, что * все * слияние будет сделано в основном потоке. То, что я ищу, - это способ слияния, происходящего в нескольких не основных потоках, так что несколько потоков могут анализировать разные файлы, но разделять их постепенный прогресс. – westsider

+0

Это способ поместить часть синтаксического анализа в main moc. U должен сделать u-код в основном потоке позже, так как я понимаю u задачу. – Alex

+0

Я пытаюсь получить несколько потоков, читающих * и * запись совместно используемому постоянному координатору хранилища, так что каждый вновь добавленный объект доступен для * all * threads, как только он будет добавлен. Это отличается от того, что только контекст управляемого объекта основного потока знает все вновь добавленные объекты. – westsider