2010-08-13 8 views
13

Przeczytałem wiele postów tutaj o NSManagedObjectContext i wielowątkowych aplikacjach. Przejrzałem także przykład CoreDataBooks, aby zrozumieć, w jaki sposób oddzielne wątki wymagają własnego NSManagedObjectContext i jak operacja składowania zostaje scalona z głównym NSManagedObjectContext. Uważam, że przykład jest dobry, ale także zbyt specyficzny dla aplikacji. Próbuję to uogólnić i zastanawiam się, czy moje podejście jest rozsądne.Ogólne podejście do NSManagedObjectContext w wielowątkowej aplikacji

Moje podejście polega na zastosowaniu ogólnej funkcji pobierania NSManagedObjectContext dla bieżącego wątku. Funkcja zwraca obiekt NSManagedObjectContext dla głównego wątku, ale utworzy nowy (lub pobierze go z pamięci podręcznej), jeśli zostanie wywołany z innego wątku. To jest następujące:

+(NSManagedObjectContext *)managedObjectContext { 
    MyAppDelegate *delegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate]; 
    NSManagedObjectContext *moc = delegate.managedObjectContext; 

    NSThread *thread = [NSThread currentThread]; 

    if ([thread isMainThread]) { 
     return moc; 
    } 

    // a key to cache the context for the given thread 
    NSString *threadKey = [NSString stringWithFormat:@"%p", thread]; 

    // delegate.managedObjectContexts is a mutable dictionary in the app delegate 
    NSMutableDictionary *managedObjectContexts = delegate.managedObjectContexts; 

    if ([managedObjectContexts objectForKey:threadKey] == nil) { 
     // create a context for this thread 
     NSManagedObjectContext *threadContext = [[[NSManagedObjectContext alloc] init] autorelease]; 
     [threadContext setPersistentStoreCoordinator:[moc persistentStoreCoordinator]]; 
     // cache the context for this thread 
     [managedObjectContexts setObject:threadContext forKey:threadKey]; 
    } 

    return [managedObjectContexts objectForKey:threadKey]; 
} 

Zapisywanie operacji jest proste, jeśli wywołane z głównego wątku. Zapisane operacje wywołane z innych wątków wymagają scalenia wewnątrz głównego wątku. Do tego mam rodzajowe commit funkcję:

+(void)commit { 
    // get the moc for this thread 
    NSManagedObjectContext *moc = [self managedObjectContext]; 

    NSThread *thread = [NSThread currentThread]; 

    if ([thread isMainThread] == NO) { 
     // only observe notifications other than the main thread 
     [[NSNotificationCenter defaultCenter] addObserver:self 
              selector:@selector(contextDidSave:) 
               name:NSManagedObjectContextDidSaveNotification 
               object:moc]; 
    } 

    NSError *error; 
    if (![moc save:&error]) { 
     // fail 
    } 

    if ([thread isMainThread] == NO) { 
     [[NSNotificationCenter defaultCenter] removeObserver:self 
                name:NSManagedObjectContextDidSaveNotification 
                object:moc]; 
    } 
} 

w funkcji contextDidSave: wykonujemy seryjnej, jeśli wywołana przez notyfikacji w commit.

+(void)contextDidSave:(NSNotification*)saveNotification { 
    MyAppDelegate *delegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate]; 
    NSManagedObjectContext *moc = delegate.managedObjectContext; 

    [moc performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) 
         withObject:saveNotification 
        waitUntilDone:YES]; 
} 

Wreszcie, oczyszczenia pamięci podręcznej NSManagedObjectContext z tym:

+(void)initialize { 
    [[NSNotificationCenter defaultCenter] addObserver:self 
              selector:@selector(threadExit) 
               name:NSThreadWillExitNotification 
               object:nil]; 
} 

+(void)threadExit { 
    MyAppDelegate *delegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate]; 
    NSString *threadKey = [NSString stringWithFormat:@"%p", [NSThread currentThread]]; 
    NSMutableDictionary *managedObjectContexts = delegate.managedObjectContexts;  

    [managedObjectContexts removeObjectForKey:threadKey]; 
} 

To kompiluje i wydaje się działać, ale wiem, gwintowania problemów może być trudne ze względu na warunki wyścigu. Czy ktoś widzi problem z tym podejściem?

Używam tego również w kontekście asynchronicznego żądania (przy użyciu ASIHTTPRequest), które pobiera niektóre dane z serwera i aktualizuje i wstawia sklep do iPhone'a. Wydaje się, że NSThreadWillExitNotification nie uruchamia się po zakończeniu żądania, a następnie ten sam wątek jest używany do kolejnych żądań. Oznacza to, że ten sam NSManagedObjectContext jest używany do oddzielnych żądań w tym samym wątku. Czy to problem?

+0

chris, mam do czynienia z podobnym problemem wielowątkowości podczas korzystania z jednego NSManagedObjectContext utworzonego w wątku głównym dla wszystkich operacji w kolejce NSoperation. Probkem przychodzi, gdy każdy wątek próbuje zapisać kontekst, aplikacja ulega awarii losowo rzucając wyjątek od danych podstawowych. Myślałem o zablokowaniu tego kontekstu podczas używania we wszystkich operacjach, aby każdy z nich miał wyłączny dostęp do kontekstu. Czytałem ur powyżej solution.sounds gud, couls u proszę wklej mi nowy kod, który użyłeś do scalenia kontekstu, a także skomentuj użycie blokady do gry –

Odpowiedz

8

Rok po opublikowaniu tego pytania, w końcu stworzyłem ramy do uogólnienia i uproszczenia mojej pracy z danymi podstawowymi. Wykracza poza pierwotne pytanie i dodaje wiele funkcji ułatwiających interakcje w Core Data.Szczegóły tutaj: https://github.com/chriscdn/RHManagedObject

0

Znalazłem rozwiązanie po lepszym zrozumieniu problemu. Moje rozwiązanie nie rozwiązuje bezpośrednio powyższego pytania, ale rozwiązuje problem, dlaczego w ogóle miałem do czynienia z wątkami.

Moja aplikacja używa biblioteki ASIHTTPRequest do asynchronicznych żądań. Pobieram niektóre dane z serwera i używam funkcji delegata requestFinished do dodawania/modyfikowania/usuwania moich obiektów danych podstawowych. Funkcja requestFinished działała w innym wątku i założyłem, że jest to naturalny efekt uboczny asynchronicznych żądań.

Po kopania głębiej stwierdziliśmy, że ASIHTTPRequest celowo kieruje wniosek w osobnym wątku, ale może być zmienione w moim podklasy ASIHTTPRequest:

+(NSThread *)threadForRequest:(ASIHTTPRequest *)request { 
    return [NSThread mainThread]; 
} 

Ta niewielka zmiana stawia requestFinished w głównym wątku, który został wyeliminowany moja potrzeba dbania o wątki w mojej aplikacji.

+0

Nie jestem pewien czy zrozumiałem. ASIHTTPRequest używa osobnego wątku (w rzeczywistości NSOperationQueue) dla żądań asynchronicznych, ale zawsze zawsze działa requestFinished na mainthread. (W najnowszym kodzie ta funkcja jest w metodzie callSelectorOnMainThread.) Powiedział, że nie widzę żadnej wady rozwiązania. – JosephH

+0

Okazało się, że requestFinished nie działał w głównym wątku, dopóki nie dodałem tych trzech linii powyżej. Czy to możliwe, że zmieniło się to za pomocą ASIHTTPRequest? Używam wersji 1.7. – chris

+0

Masz rację. Delegat ASIHTTPRequest jest uruchamiany w głównym wątku, ale implementowałem podklasę ASIHTTPRequest i umieszczając mój kod w metodzie 'requestFinished:'. To niekoniecznie zostanie wywołane w głównym wątku. dzięki – chris