Wątek roboczy NSOperation i NSOperationQueue a wątek główny

81

Muszę przeprowadzić serię operacji pobierania i zapisywania bazy danych w mojej aplikacji. Używam NSOperationi NSOperationQueuedo tego samego.

To jest scenariusz aplikacji:

  • Pobierz wszystkie kody pocztowe z miejsca.
  • Dla każdego kodu pocztowego pobierz wszystkie domy.
  • Dla każdego domu należy pobrać dane dotyczące mieszkańców

Jak powiedziałem, zdefiniowałem NSOperationdla każdego zadania. W pierwszym przypadku (Zadanie1) wysyłam do serwera żądanie pobrania wszystkich kodów pocztowych. Delegat w ramach NSOperationotrzyma dane. Dane te są następnie zapisywane w bazie danych. Operacja na bazie danych jest zdefiniowana w innej klasie. Z NSOperationklasy wykonuję wywołanie funkcji zapisu zdefiniowanej w klasie bazy danych.

Moje pytanie brzmi, czy operacja zapisu do bazy danych występuje w wątku głównym, czy w wątku w tle? Ponieważ wywoływałem go w ramach NSOperation, spodziewałem się, że będzie działał w innym wątku (nie MainThread) jako NSOperation. Czy ktoś może wyjaśnić ten scenariusz podczas pracy z NSOperationi NSOperationQueue.

Zach
źródło
3
Jeśli dodasz operacje do głównej kolejki, zostaną one wykonane w głównym wątku. Jeśli utworzysz własną NSOperationQueue i dodasz do niej operacje, będą one wykonywane w wątkach tej kolejki.
Cy-4AH
1
Nie sądzę, abyś uzyskał lepszą odpowiedź niż podał @ Cy-4AH, chyba że uzyskasz bardziej szczegółowy / wyślesz jakiś kod. Powiem, że zawsze możesz umieścić punkt przerwania w kodzie, a kiedy się wyłączy, pokaże ci, w którym wątku znajduje się ślad.
Brad Allred
Co oznacza „Delegat w ramach NSOperation otrzyma dane”. oznaczać? Ani NSOperationnie NSOperationQueuezawierają właściwości delegata.
Jeffery Thomas
Możesz również przekazać swoje wywołanie delegata do głównego wątku, zamiast robić jakiekolwiek przypuszczenia dotyczące bieżącego wątku ...
Wain

Odpowiedzi:

175

Moje pytanie brzmi, czy operacja zapisu bazy danych występuje w wątku głównym, czy w wątku w tle?

Jeśli utworzysz NSOperationQueueod podstaw, jak w:

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

Będzie w wątku w tle:

Kolejki operacji zwykle udostępniają wątki używane do uruchamiania ich operacji. W systemie OS X 10.6 i nowszych kolejki operacji używają biblioteki libdispatch (znanej również jako Grand Central Dispatch) do inicjowania wykonywania swoich operacji. W rezultacie operacje są zawsze wykonywane w oddzielnym wątku , niezależnie od tego, czy są oznaczone jako operacje współbieżne czy nierównoczesne

Chyba że używasz mainQueue:

NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];

Możesz również zobaczyć taki kod:

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

   // Background work

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

Oraz 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)                          
              });
});

NSOperationQueuedaje lepszą kontrolę nad tym, co chcesz robić. Możesz tworzyć zależności między dwiema operacjami (pobieranie i zapisywanie w bazie danych). Aby przekazać dane między jednym blokiem a drugim, możesz założyć na przykład, że NSDatabę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];

Edycja: Dlaczego używam __weakreferencji dla operacji, można znaleźć tutaj . Ale w skrócie należy unikać zachowywania cykli.

Rui Peres
źródło
3
odpowiedź jest prawidłowa, ale proszę zauważyć, że poprawna linia kodu to: NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];i że NSOperationQueue w głównym wątku nie może zostać zawieszone.
Gianluca P.
2
@ jacky-boy Poza interesem, po co w końcowym fragmencie kodu używać słabych odniesień do downloadOperation i saveToDataBaseOperation?
Michael Waterfall
RuiAAPeres, hej, jestem ciekaw, dlaczego w końcowym fragmencie kodu użyto słabych odwołań? Czy mógłbyś rzucić na to trochę światła?
Pavan,
@Pavan chodzi o to, że jeśli zdarzy ci się odwołać do tej samej operacji bloku w jej własnym bloku, otrzymasz cykl przechowywania. Dla porównania: conradstoll.com/blog/2013/1/19/…
Rui Peres
więc który blok jest taki sam jak blok, który się w nim znajduje?
Pavan
16

Jeśli chcesz wykonać operację zapisu do bazy danych w wątku w tle, musisz utworzyć NSManagedObjectContextdla tego wątku.

Tło można utworzyć NSManagedObjectContextw metodzie startowej odpowiedniej NSOperationpodklasy.

Sprawdź dokumentację Apple pod kątem współbieżności z danymi podstawowymi.

Możesz również utworzyć, NSManagedObjectContextktóry wykonuje żądania we własnym wątku w tle, tworząc go za pomocą NSPrivateQueueConcurrencyTypei wykonując żądania wewnątrz swojej performBlock:metody.

serrrgi
źródło
17
Dlaczego uważasz, że to pytanie jest związane z danymi podstawowymi?
Nikolai Ruhe
11

Z NSOperationQueue

W systemie iOS 4 i nowszych kolejki operacji używają Grand Central Dispatch do wykonywania operacji. W wersjach starszych niż iOS 4 tworzą oddzielne wątki dla operacji niewspółbieżnych 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 chcieć stworzyć swój własny "wątek bazy danych" przez tworzenie podklas NSThreadi wysyłanie do niego wiadomości za pomocą performSelector:onThread:.

ilya n.
źródło
dzięki alot ... u save my life: [NSOperationQueue new] // post-iOS4, gwarantowane, że nie będzie głównym wątkiem
ikanimo
9

Wątek wykonania NSOperation zależy od NSOperationQueuemiejsca dodania operacji. Spójrz na to stwierdzenie w swoim kodzie -

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

Wszystko to zakłada, że ​​nie wykonałeś żadnego dalszego wątku, którego mainmetoda NSOperationjest prawdziwym potworem, w którym napisałeś instrukcje pracy (spodziewasz się).

Jednak w przypadku operacji równoległych scenariusz jest inny. Kolejka może utworzyć wątek dla każdej współbieżnej operacji. Chociaż nie jest to gwarantowane i zależy od zasobów systemowych w porównaniu z zapotrzebowaniem na zasoby operacyjne w tym punkcie systemu. Możesz kontrolować współbieżność kolejki operacji przez tomaxConcurrentOperationCount właściwości.

EDYTUJ -

Uważam, że Twoje pytanie jest interesujące i sam przeprowadziłem analizę / logowanie. Mam NSOperationQueue utworzony 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 utworzyłem NSOperation i dodałem go 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. Okazało się, że bieżący obiekt wątku nie jest głównym obiektem wątku.

Tak więc niestandardowe tworzenie kolejki w głównym wątku (bez współbieżności między jej operacjami) niekoniecznie oznacza, że ​​operacje będą wykonywane szeregowo w samym wątku głównym.

Ashok
źródło
Uważam, że używanie [[NSOperationQueue alloc] init]gwarancji, że kolejka będzie używać podstawowego domyślnego globalnego DispatchQueue w tle. Tak więc każda niestandardowa zainicjowana OperationQueue będzie przekazywać swoje zadania do DispatchQueues w tle, a tym samym przekazywać zadania do wątków, które nie są głównym wątkiem. Aby wprowadzić operacje do głównego wątku, musisz uzyskać główną OperationQueue za pomocą czegoś podobnego [NSOperationQueue mainQueue]. W ten sposób uzyskuje się główną kolejkę operacji, która wewnętrznie używa głównej DispatchQueue, a zatem ostatecznie przekazuje zadania do głównego wątku.
Gordonium,
1

Podsumowanie z dokumentacji to operations are always executed on a separate thread(po iOS 4 sugeruje kolejki operacji bazowych GCD).

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

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

Podczas uruchamiania w wątku trywialne jest użycie GCD / libdispatch do uruchomienia czegoś w głównym wątku, niezależnie od tego, czy chodzi o dane podstawowe, interfejs użytkownika czy inny kod wymagany do uruchomienia w głównym wątku:

dispatch_async(dispatch_get_main_queue(), ^{
    // this is now running on the main thread
});
duncanwilcox
źródło
-2

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

Ostrokrzew
źródło
1
Pytanie nie wspomina o konkretnej bazie danych, takiej jak fmdb czy sqlite.
Nikolai Ruhe
To prawda, przyjąłem założenie oparte na kontekście. Gdyby aplikacja łączyła się z wielodostępną, bezpieczną wątkowo bazą danych, taką jak MySQL, pytanie nie miałoby sensu. Istnieją inne bazy danych dla jednego użytkownika, których można użyć, ale SQLite jest zdecydowanie najpopularniejszym.
Holly