2011-01-15 25 views
5

Ho letto il capitolo di Marcus Zarra sul multithreading nel suo libro di Core Data e ho esaminato abbastanza attentamente il suo codice di esempio. Ma il suo codice e altri che ho trovato altrove sembrano essere focalizzati in processi in background che fanno non devono essere consapevoli l'uno dell'altro. Questi esempi sono utili per importare una struttura ad albero, ma non riguardano l'importazione di una struttura più generale (complessa), come un grafo aciclico diretto.Core Data e multithreading

Nel mio caso, sto tentando di analizzare una gerarchia di classi C++ e vorrei utilizzare il maggior numero possibile di NSOperations. Vorrei creare un'istanza NSManagedObject per ogni classe incontrata e vorrei unire diversi NSManagedObjectContexts ogni volta che ne viene salvato uno.

Per inciso: sono in grado di far funzionare le cose con una sola NSOperation che itera su file e si analizza uno alla volta. In questa implementazione, il -mergeChanges: approccio che chiama -mergeChangesFromContextDidSaveNotification: sul MOC del thread principale funziona bene.

Ma idealmente, avrei una NSOperation iterare sui file sorgente e generare NSOperations per analizzare ogni file. Ho provato diversi approcci - ma non riesco a farlo bene. Il più promettente era che ogni NSOperation osservasse NSManagedObjectContextDidSaveNotification. Con -mergeChanges: simile a questo:

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

In sostanza, l'analisi del NSOperation main() controllati Ivar 'needsToMerge' periodicamente. Se era vero, allora -mergeChangesFromContextDidSaveNotification: veniva chiamato sul MOC locale con NSNotifications memorizzate nella cache. E quindi needsToMerge è stato ripristinato. Se la notifica è stata originata localmente, al thread principale è stato detto di eseguire -mergeChangesFromContextDidSaveNotification: sul suo MOC.

sono sicuro che ci sia una buona ragione per cui questo non ha funzionato e perché ottengo questo:

avvertimento: Annullamento di chiamata - Codice objc sullo stack del thread corrente rende questo non sicuri.

ho anche cercato di usare il blocco di NSPeristentStoreCoordinator per controllare l'accesso - ma questo è problematico se si tiene durante una chiamata per -save di NSManagedObjectContext: metodo perché -save: notificherà osservatori interessati di salvare l'evento e -mergeChangesFromContextDidSaveNotification: sembra bloccare il tentativo di acquisire il blocco di PSC.

Sembra proprio che questo dovrebbe essere molto più semplice.

risposta

2

penso di hade lo stesso problema ed ecco come ho risolto:

Creare una classe NSOperation personalizzato in cui si definiscono:

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

nel metodo principale NSOperation prima di salvare il contesto prima applicare tutte le modifiche richieste:

[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] 

Nota che ho usato un blocco, questo perché NSMutableArray non è thread-safe e voglio accedere in modo sicuro changeNotifications. changeNotifications è l'array in cui sono salvate tutte le modifiche che devono essere applicate prima di salvare il contesto.

Ed ecco il metodo di unione, modificato in modo che tutte le modifiche che devono essere unite da NSOperation vengano unite utilizzando il thread corretto. Si noti che questo metodo viene chiamato da altri thread che il vostro NSOperation uno, pertanto è necessario per bloccare l'accesso a 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  

} 

Spero che questo aiuto! Questo metodo non è solido al 100% ci possono essere alcuni casi in cui una notifica di modifica può arrivare troppo tardi. In tal caso, il metodo di salvataggio del contesto restituirà un errore e dovrai ricaricare NSManagedObject e salvarlo di nuovo. Nel caso in cui siano necessari ulteriori dettagli, fatemelo sapere.

+0

questa è una buona soluzione. Per quanto mi riguarda, probabilmente dovrei applicare changeNotifications prima di cercare un oggetto oltre a prima del salvataggio, ma sarebbe facile. A quanto ho capito, tutti i thread (eccetto il thread principale) sarebbero istanze di questa sottoclasse. È questo il caso? – westsider

+0

@Westsider tutti gli NSOper che devono unire il contesto dovrebbero essere una sottoclasse di esso. Fondamentalmente suggerisco di creare una classe NSOperationManagedOobjectContextAware che abbia un metodo mergeChanges e le 3 proprietà changeNotifications, changeNotificationsLock, localManagedObjectContext –

0

Questa due pace del codice di lavoro ora correttamente nella mia domanda:

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

Naturalmente, managedObjectContext significa:

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

attenzione se u bisogno di togliere qualcosa prima che la u faccia aggiornamenti dal principale moc. Ho un tempo pazzesco e un sacco di errori di debug difficili, mentre capisco che non posso usare il moc dal thread principale mentre da qualche altra parte sto elaborando altre modifiche con lo stesso contenuto.

+0

Non vedo come questo potrebbe funzionare per risolvere il mio problema. Sembra che * tutti * la fusione verrà eseguita nel thread principale. Quello che sto cercando è un modo per fare in modo che la fusione avvenga in più thread non main, in modo che diversi thread possano analizzare file diversi ma condividere i loro progressi incrementali. – westsider

+0

Questo è un modo per mettere parte del parsing al main moc. U devi fare il codice sul thread principale più tardi perché ho capito u task. – Alex

+0

Sto cercando di ottenere più thread che leggono * e * scrivendo al coordinatore dell'archivio persistente condiviso, in modo tale che ogni oggetto appena aggiunto sia disponibile per i thread * all * non appena viene aggiunto. Questo è diverso dal semplice fatto che il contesto dell'oggetto gestito del thread principale sia consapevole di tutti gli oggetti aggiunti di recente. – westsider