11

Mam parametr NSOperationQueue, który importuje obiekty do danych podstawowych, które otrzymuję z interfejsu WWW. Każda operacja ma prywatny obiekt child managedObjectContext głównego obiektu zarządzanego mojej aplikacji zarządzanej. Każda operacja pobiera obiekt do zaimportowania i sprawdza, czy obiekt już istnieje, w którym to przypadku aktualizuje istniejący obiekt. Jeśli obiekt nie istnieje, tworzy ten nowy obiekt. Te zmiany w prywatnych kontekstach podrzędnych są następnie propagowane do głównego kontekstu obiektu zarządzanego.Import wielowątkowości danych podstawowych (zduplikowane obiekty)

Ta konfiguracja działa bardzo dobrze dla mnie, , ale istnieje duplikat problemu.

Gdy mam ten sam obiekt importowany w dwóch różnych równoległych operacjach, otrzymuję zduplikowane obiekty, które mają dokładnie takie same dane. (Oboje sprawdzają, czy obiekt istnieje, i nie wydaje im się, aby już istniał). Powodem, dla którego będę miał dwa takie same obiekty importujące w tym samym czasie, jest to, że często będę przetwarzał "nowe" wywołanie api, jak również wywoływanie "api". Ze względu na asynchroniczny charakter mojej konfiguracji, trudno jest zagwarantować, że nigdy nie będę mieć duplikatów obiektów próbujących zaimportować.

Moje pytanie brzmi: jaki jest najlepszy sposób rozwiązania tego konkretnego problemu? Zastanowiłem się nad ograniczeniem importu do max. Operacji współbieżnych do 1 (nie podoba mi się to z powodu wydajności). Podobnie zastanawiałem się nad wymaganiem zapisu po każdej operacji importowania i próbą obsługi łączenia kontekstów. Zastanowiłem się również, czy później wyczyścić dane, aby od czasu do czasu oczyścić duplikaty. W końcu rozważałem tylko obsługę duplikatów we wszystkich żądaniach pobierania. Ale żadne z tych rozwiązań nie wydaje mi się wspaniałe, a być może istnieje proste rozwiązanie, na które nie mogłem sobie pozwolić.

+0

Doskonałe pytanie. Mam taki sam problem i już miałem zadać to pytanie. – djskinner

Odpowiedz

2

Zacznij od utworzenia zależności między operacjami. Upewnij się, że nie można ukończyć, dopóki nie zostanie uzależniona.

Wyjazd http://developer.apple.com/library/mac/documentation/Cocoa/Reference/NSOperation_class/Reference/Reference.html#//apple_ref/occ/instm/NSOperation/addDependency:

Każda operacja powinna zadzwonić zapisać kiedy skończył. Następnie chciałbym spróbować metodologia Find-A-Tworzenie zasugerował tutaj:

https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreData/Articles/cdImporting.html

To będzie rozwiązać problem duplikatów, a może prawdopodobnie spowodować robisz mniej pobieraniom (które są drogie i powolne, więc spuścić bateria szybko).

Można również utworzyć globalny kontekst podrzędny, aby obsłużyć wszystkie importowane produkty, a następnie połączyć całą ogromną rzecz na końcu, ale tak naprawdę sprowadza się to do tego, jak duży jest zbiór danych i do waszej pamięci.

+0

Sądzę, że stosuję się do schematu zaproponowanego w metodologii Znajdź lub Utwórz. Mój problem z duplikatami występuje zawsze, ponieważ robię dwa z tych importów w tym samym czasie. Powinienem być bardziej przejrzysty, ale jedna operacja w mojej konfiguracji może obsługiwać całą tablicę danego obiektu. Na przykład jedną operacją będzie obsługa tablicy zwróconej przez żądanie "get", a inną operacją będzie obsługa pojedynczego obiektu zwróconego przez "nowe" żądanie. – hatunike

+0

Zobacz powyższą edycję, ale myślę, że używanie addDependency: w podklasach NSOperation jest krytyczne, jeśli chcesz móc przetworzyć więcej niż jeden element na raz. Można oczywiście po prostu przejść do 1 równoczesnej operacji, ale to, co naprawdę tutaj mamy, to problem zależności. –

+0

A więc sugerujesz, aby operacje importujące ten sam typ jednostki były operacjami zależnymi? To nadal pozwala na równoległe operacje, gdy obiekty są innego rodzaju, ale wymagają zamówienia, w którym może wystąpić problem? Myślę, że podoba mi się ta sugestia. Zbadam to jeszcze bardziej. Dzięki za sugestie! – hatunike

4

Więc problem jest:

  • konteksty są scratchpad - dopóki zapisać, zmiany wprowadzone w nich nie są wypychane do trwałego magazynu;
  • chcesz, aby jeden kontekst był świadomy zmian dokonanych na innym, które nie zostały jeszcze przekazane.

Dla mnie nie brzmi to jak połączenie kontekstów będzie działać - konteksty nie są bezpieczne dla wątków. Dlatego, aby nastąpiło scalenie, nic innego nie może być kontynuowane w wątku/kolejce innego kontekstu. Dlatego nigdy nie będziesz w stanie wyeliminować ryzyka, że ​​nowy obiekt zostanie wstawiony, podczas gdy inny kontekst jest częściowo w trakcie procesu wstawiania.

Dodatkowe obserwacje:

  • SQLite nie jest bezpieczeństwo wątków w każdym sensie praktycznym;
  • dlatego wszystkie wycieczki do magazynu trwałego są przekształcane do postaci szeregowej niezależnie od sposobu ich wystawienia.

Mając na uwadze problem i SQLite ograniczenia, w mojej aplikacji mamy przyjęte ramy przy czym połączenia internetowe są naturalnie współbieżne zgodnie NSURLConnection retrospektywnego analizowania wyników (JSON parsowania oraz niektóre rodzaje rybołówstwa na wynik) występuje jednocześnie, a następnie krok znajdowania lub tworzenia jest kierowany do kolejki szeregowej.

Bardzo mało czasu przetwarzania zostaje utracone przez serializację, ponieważ wycieczki SQLite i tak będą serializowane, i to przeważająca większość z serializowanych rzeczy.

+0

Tak, to użyteczne informacje. Rozwiązałem niektóre problemy z SQL, o których mówisz, po skonfigurowaniu stosu danych podstawowych. Mam prywatny kontekst typu NSPrivateQueueConcurrencyType jedynym zadaniem tego kontekstu jest zapis do magazynu trwałego. Z tego kontekstu mam kontekst podrzędny typu NSMainQueueConcurrencyType, którego używam jako głównego kontekstu mojej aplikacji. Piękno tej konfiguracji mogę kontrolować, pisząc do mojego stałego sklepu. Moja konfiguracja jest zgodna z tymi ustawieniami, jeśli ktokolwiek jest zainteresowany: http://www.cocoanetics.com/2012/07/multi-context-coredata/ – hatunike

+0

Na tej linii myślenia nie chodzi o to, do czego ma służyć ta funkcja, ale Odkryłem, że za pomocą 'UIApplication -beginBackgroundTaskWithExpirationHandler:' gdy aplikacja rezygnuje z aktywności i robi drogie blokowanie danych podstawowych, jest to całkowicie akceptowalne z punktu widzenia Apple'a. Upewnij się, że jest to przerywane w przypadku, gdy aplikacja ponownie się uruchomi. To tam robimy nasze skreślenia. Jeśli jesteś w stanie odroczyć pisanie na dysk, to jest to naprawdę dobra okazja. – Tommy

+0

To jest wspaniała rada! Nie myślałem o tym dla skasowania. Dzięki. – hatunike

1

Od jakiegoś czasu borykam się z tym samym problemem. Dyskusja na ten temat dała mi do tej pory kilka pomysłów, którymi podzielę się teraz.

Należy pamiętać, że jest to w zasadzie niesprawdzone, ponieważ w moim przypadku bardzo rzadko zdarza się to tylko podczas testu i nie ma dla mnie łatwego sposobu na odtworzenie tego.

Mam taką samą konfigurację stosu CoreData - Główny MOC w prywatnej kolejce, która ma dziecko w głównej kolejce i jest używana jako główny kontekst aplikacji. Ostatecznie, operacje importu zbiorczego (find-or-create) są przekazywane do trzeciego MOC-a za pomocą kolejki tła. Po zakończeniu operacji zapisywane są dane do PSC.

Przeniosłem cały stos danych podstawowych z AppDelegate do oddzielnej klasy (AppModel), która zapewnia aplikacji dostęp do zagregowanego obiektu głównego domeny (Player), a także funkcję pomocniczą do wykonywania operacji w tle na modelu (performBlock:onSuccess:onError:).

Na szczęście dla wszystkich głównych operacji CoreData są przekazywane przez tę metodę, więc jeśli mogę zapewnić, że te operacje są uruchamiane seryjnie, powielony problem powinien zostać rozwiązany.

- (void) performBlock: (void(^)(Player *player, NSManagedObjectContext *managedObjectContext)) operation onSuccess: (void(^)()) successCallback onError:(void(^)(id error)) errorCallback 
{ 
    //Add this operation to the NSOperationQueue to ensure that 
    //duplicate records are not created in a multi-threaded environment 
    [self.operationQueue addOperationWithBlock:^{ 

     NSManagedObjectContext *managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 
     [managedObjectContext setUndoManager:nil]; 
     [managedObjectContext setParentContext:self.mainManagedObjectContext]; 

     [managedObjectContext performBlockAndWait:^{ 

      //Retrive a copy of the Player object attached to the new context 
      id player = [managedObjectContext objectWithID:[self.player objectID]]; 
      //Execute the block operation 
      operation(player, managedObjectContext); 

      NSError *error = nil; 
      if (![managedObjectContext save:&error]) 
      { 
       //Call the error handler 
       dispatch_async(dispatch_get_main_queue(), ^{ 
        NSLog(@"%@", error); 
        if(errorCallback) return errorCallback(error); 
       }); 
       return; 
      } 

      //Save the parent MOC (mainManagedObjectContext) - WILL BLOCK MAIN THREAD BREIFLY 
      [managedObjectContext.parentContext performBlockAndWait:^{ 
       NSError *error = nil; 
       if (![managedObjectContext.parentContext save:&error]) 
       { 
        //Call the error handler 
        dispatch_async(dispatch_get_main_queue(), ^{ 
         NSLog(@"%@", error); 
         if(errorCallback) return errorCallback(error); 
        }); 
        return; 
       } 
      }]; 

      //Attempt to clear any retain cycles created during operation 
      [managedObjectContext reset]; 

      //Call the success handler 
      dispatch_async(dispatch_get_main_queue(), ^{ 
       if (successCallback) return successCallback(); 
      }); 
     }]; 
    }]; 
} 

Co ja tu dodać, że mam nadzieję, że ma zamiar rozwiązać ten problem dla mnie jest owijanie całość w addOperationWithBlock. Moja kolejka operacja jest po prostu skonfigurowany w następujący sposób:

single.operationQueue = [[NSOperationQueue alloc] init]; 
[single.operationQueue setMaxConcurrentOperationCount:1]; 

W mojej klasie API, mogę wykonać import na mojej pracy w następujący sposób:

- (void) importUpdates: (id) methodResult onSuccess: (void (^)()) successCallback onError: (void (^)(id error)) errorCallback 
{ 
    [_model performBlock:^(Player *player, NSManagedObjectContext *managedObjectContext) { 
     //Perform bulk import for data in methodResult using the provided managedObjectContext 
    } onSuccess:^{ 
     //Call the success handler 
     dispatch_async(dispatch_get_main_queue(), ^{ 
      if (successCallback) return successCallback(); 
     }); 
    } onError:errorCallback]; 
} 

Teraz z NSOperationQueue w miejscu powinno być dłużej możliwe, aby więcej niż jedna operacja wsadowa miała miejsce w tym samym czasie.

+0

Tak, jest to potwierdzone rozwiązanie konkretnego problemu, który mam. Dla mnie nie dopuszczenie, aby kolejka miała więcej niż 1 równoległą operację, jest zbyt dużym hitem wydajności. Ale dla wielu osób może to być świetne rozwiązanie. – hatunike

Powiązane problemy