Pytanie NSSperation i NSOperationQueue wątek roboczy a wątek główny


Muszę przeprowadzić serię operacji zapisu i zapisu bazy danych w mojej aplikacji. Używam NSOperation i NSOperationQueue za to samo.

To jest scenariusz zastosowania:

  • Pobierz wszystkie kody pocztowe z danego miejsca.
  • Dla każdego kodu pocztowego należy pobrać wszystkie domy.
  • Dla każdego domu pobierz szczegóły mieszkańca

Jak powiedziałem, zdefiniowałem NSOperation dla każdego zadania. W pierwszym przypadku (zadanie 1) wysyłam żądanie do serwera, aby pobrać wszystkie kody pocztowe. Delegat w NSOperation otrzyma dane. Te dane są następnie zapisywane w bazie danych. Operacja bazy danych jest zdefiniowana w innej klasie. Od NSOperation klasa Wywołuję funkcję zapisu zdefiniowaną w klasie bazy danych.

Moje pytanie brzmi, czy operacja zapisu bazy danych występuje w wątku głównym czy w wątku tła? Jak to nazywałem w NSOperation Spodziewałem się, że uruchomi się w innym wątku (nie MainThread) jako NSOperation. Czy ktoś może wyjaśnić ten scenariusz podczas rozpatrywania NSOperation i NSOperationQueue.


76
2017-10-24 14:49


pochodzenie


Jeśli dodasz operacje do głównej kolejki, zostaną one wykonane w głównym wątku. Jeśli utworzysz własny NSOperationQueue i dodasz do niego operacje, zostaną one wykonane w wątkach tej kolejki. - Cy-4AH
Nie sądzę, że dostaniesz lepszą odpowiedź niż @ Cy-4AH, chyba że dostaniesz bardziej konkretny / napisać jakiś kod. Powiem, że zawsze możesz umieścić punkt przerwania w kodzie, a kiedy się potknie, pokaże ci, w której wątku znajduje się ślad. - Brad Allred
Co robi "Delegat w ramach NSOperation otrzyma dane." oznaczać? Ani NSOperation ani NSOperationQueue zawierają właściwości delegatów. - Jeffery Thomas
Możesz również popchnąć delegowane połączenie na główny wątek, zamiast zakładać założenia dotyczące bieżącego wątku ... - Wain


Odpowiedzi:


Moje pytanie brzmi, czy operacja zapisu bazy danych występuje w głównej   wątku lub w wątku tła?

Jeśli utworzysz NSOperationQueue od zera, jak w:

NSOperationQueue *myQueue = [[NSOperationQueue alloc] init];

Będzie w wątku tła:

Kolejki operacji zazwyczaj zawierają wątki używane do ich uruchomienia   operacje. W systemie OS X 10.6 i nowszych kolejki operacji używają   biblioteka libdispatch (znana również jako Grand Central Dispatch) do zainicjowania   wykonanie ich operacji. W rezultacie operacje są zawsze   wykonywane w oddzielnym wątkuniezależnie od tego, czy są   wyznaczone jako operacje współbieżne lub niejednoczesne

Chyba że korzystasz z mainQueue:

NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];

Możesz również zobaczyć kod:

NSOperationQueue *myQueue = [[NSOperationQueue alloc] init];
[myQueue addOperationWithBlock:^{

   // Background work

    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        // Main thread work (UI usually)
    }];
}];

I wersja GCD:

dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void)
             {
              // Background work            
             dispatch_async(dispatch_get_main_queue(), ^(void)
              {
                   // Main thread work (UI usually)                          
              });
});

NSOperationQueue daje lepszą kontrolę nad tym, co chcesz robić. Możesz tworzyć zależności między dwiema operacjami (pobierz i zapisz do bazy danych). Aby przekazać dane między jednym blokiem a drugim, można założyć na przykład, że a NSData będzie pochodził z serwera, więc:

__block NSData *dataFromServer = nil;
NSBlockOperation *downloadOperation = [[NSBlockOperation alloc] init];
__weak NSBlockOperation *weakDownloadOperation = downloadOperation;

[weakDownloadOperation addExecutionBlock:^{
 // Download your stuff  
 // Finally put it on the right place: 
 dataFromServer = ....
 }];

NSBlockOperation *saveToDataBaseOperation = [[NSBlockOperation alloc] init];
__weak NSBlockOperation *weakSaveToDataBaseOperation = saveToDataBaseOperation;

 [weakSaveToDataBaseOperation addExecutionBlock:^{
 // Work with your NSData instance
 // Save your stuff
 }];

[saveToDataBaseOperation addDependency:downloadOperation];

[myQueue addOperation:saveToDataBaseOperation];
[myQueue addOperation:downloadOperation];

Edytować: Dlaczego używam __weak odniesienie do operacji, można znaleźć tutaj. Ale w pigułce jest unikanie cykli zatrzymania.


164
2017-11-02 21:07



odpowiedź jest poprawna, ale pamiętaj, że poprawna linia kodu to: NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; i że NSOperationQueue w głównym wątku nie może zostać zawieszone. - Gianluca P.
@ jacky-boy Ciekawe, dlaczego użyto słabych odniesień do downloadOperation i saveToDataBaseOperation w ostatecznym fragmencie kodu? - Michael Waterfall
RuiAAPeres, hej, jestem ciekawy, dlaczego w ostatecznym fragmencie kodu są używane słabe referencje? Czy możesz rzucić trochę światła na to, proszę? - Pavan
@Pavan pomysł polega na tym, że jeśli odwołujesz się do tej samej operacji bloku w swoim własnym bloku, masz cykl zatrzymania. Na przykład: conradstoll.com/blog/2013/1/19/... - Peres
więc który blok jest taki sam jak blok, który działa w nim? - Pavan


Jeśli chcesz wykonać operację zapisu bazy danych w wątku tła, musisz utworzyć NSManagedObjectContext dla tego wątku.

Możesz stworzyć tło NSManagedObjectContext w metodzie początkowej twojej właściwej NSOperation podklasa.

Sprawdź dokumentację Apple dla Współbieżność z danymi podstawowymi.

Możesz również utworzyć NSManagedObjectContext który wykonuje żądania w wątku w tle, tworząc go za pomocą NSPrivateQueueConcurrencyType i wykonywanie żądań wewnątrz performBlock: metoda.


16
2017-10-30 09:31



Co sprawia, że ​​myślisz, że to pytanie wiąże się z Core Data? - Nikolai Ruhe


Od NSOperationQueue

W systemie iOS 4 i nowszych kolejki operacji używają Grand Central Dispatch do wykonywania operacji. Przed iOS 4 tworzą osobne wątki dla operacji niejednoczesnych i uruchamiają operacje współbieżne z bieżącego wątku.

Więc,

[NSOperationQueue mainQueue] // added operations execute on main thread
[NSOperationQueue new] // post-iOS4, guaranteed to be not the main thread

W twoim przypadku możesz utworzyć swój własny "wątek bazy danych" poprzez podklasowanie NSThread i wysyłaj do niego wiadomości za pomocą performSelector:onThread:.


9
2017-11-01 13:32



dziękuję ... uratujesz mi życie: [NSOperationQueue new] // post-iOS4, gwarantowane, że nie jest głównym wątkiem - ikanimo


Wątek wykonywania NSOperation zależy od NSOperationQueue gdzie dodałeś operację. Wypatruj tego stwierdzenia w swoim kodzie -

[[NSOperationQueue mainQueue] addOperation:yourOperation]; // or any other similar add method of NSOperationQueue class

Wszystko to zakłada, że ​​nie robiłeś nic więcej main metoda NSOperation który jest prawdziwym potworem, w którym napisane są instrukcje pracy, które masz (oczekuje się).

Jednak w przypadku operacji współbieżnych scenariusz jest inny. Kolejka może utworzyć wątek dla każdej operacji współbieżnej. Chociaż nie jest gwarantowany i zależy od zasobów systemowych i zapotrzebowania na zasoby operacyjne w tym momencie w systemie. Możesz kontrolować współbieżność kolejki operacji według jej maxConcurrentOperationCount własność.

EDYTOWAĆ -

Znalazłem twoje pytanie interesujące i sam wykonałem analizę / rejestrację. Mam NSOperationQueue utworzone w głównym wątku w ten sposób -

self.queueSendMessageOperation = [[[NSOperationQueue alloc] init] autorelease];

NSLog(@"Operation queue creation. current thread = %@ \n main thread = %@", [NSThread currentThread], [NSThread mainThread]);
self.queueSendMessageOperation.maxConcurrentOperationCount = 1; // restrict concurrency

Następnie przystąpiłem do tworzenia NSOperation i dodałem je za pomocą addOperation. W głównej metodzie tej operacji, gdy sprawdziłem bieżący wątek,

NSLog(@"Operation obj =  %@\n current thread = %@ \n main thread = %@", self, [NSThread currentThread], [NSThread mainThread]);

nie był to główny wątek. I odkrył, że bieżący obiekt wątku nie jest głównym obiektem wątku.

Zatem niestandardowe tworzenie kolejki na głównym wątku (bez współbieżności między jej operacjami) nie musi oznaczać, że operacje będą wykonywane szeregowo w samym wątku głównym.


9
2017-10-30 05:07



Wierzę, że używam [[NSOperationQueue alloc] init] zapewnia, że ​​kolejka będzie używać bazowego domyślnego tła globalnego DispatchQueue. Tak więc każda zainicjowana operacja OperationQueue będzie przekazywać swoje zadania do tła DispatchQueues, a zatem przekazywać zadania do wątków, które nie są głównym wątkiem. Aby przekazać operacje do głównego wątku, musisz uzyskać główną operację Operationueue przy użyciu czegoś podobnego [NSOperationQueue mainQueue]. W ten sposób uzyskuje się główną kolejkę operacji, która wewnętrznie korzysta z głównej klasy DispatchQueue i dlatego ostatecznie przesyła zadania do głównego wątku. - Gordonium


Podsumowanie od dokumentów jest operations are always executed on a separate thread (Post iOS 4 implikuje kolejki operacji bazowych GCD).

Sprawdzenie, czy rzeczywiście działa w wątku innym niż główny, jest banalne:

NSLog(@"main thread? %@", [NSThread isMainThread] ? @"YES" : @"NO");

Podczas działania w wątku, używanie GCD / libdispatch do uruchomienia czegoś w głównym wątku jest trywialne, niezależnie od tego, czy dane podstawowe, interfejs użytkownika lub inny kod wymagane do uruchomienia w głównym wątku:

dispatch_async(dispatch_get_main_queue(), ^{
    // this is now running on the main thread
});

1
2017-11-02 19:52





Jeśli robisz jakieś nietrywialne wątki, powinieneś użyć FMDatabaseQueue.


-2
2017-10-30 02:10



Pytanie nie zawiera konkretnej bazy danych, takiej jak fmdb lub sqlite. - Nikolai Ruhe
To prawda, zrobiłem założenie oparte na kontekście. Jeśli aplikacja łączyła się z wielodostępną, bezpieczną dla bazy danych bazą danych, taką jak MySQL, pytanie nie miałoby sensu. Istnieją inne bazy danych dla pojedynczego użytkownika, które mogą być używane, ale SQLite jest najczęstszym do tej pory. - Holly