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.


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.


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.


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:.


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.


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

-2

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


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.