NSOperation- und NSOperationQueue-Arbeitsthread gegen Hauptthread

80

Ich muss eine Reihe von Download- und Datenbankschreibvorgängen in meiner App ausführen. Ich benutze das NSOperationund NSOperationQueuefür das gleiche.

Dies ist ein Anwendungsszenario:

  • Holen Sie sich alle Postleitzahlen von einem Ort.
  • Holen Sie für jede Postleitzahl alle Häuser.
  • Holen Sie sich für jedes Haus die Details der Bewohner

Wie gesagt, ich habe NSOperationfür jede Aufgabe eine definiert . Im ersten Fall (Task1) sende ich eine Anfrage an den Server, um alle Postleitzahlen abzurufen. Der Delegierte innerhalb der NSOperationwird die Daten erhalten. Diese Daten werden dann in die Datenbank geschrieben. Die Datenbankoperation wird in einer anderen Klasse definiert. Von der NSOperationKlasse aus rufe ich die in der Datenbankklasse definierte Schreibfunktion auf.

Meine Frage ist, ob der Datenbankschreibvorgang im Hauptthread oder in einem Hintergrundthread erfolgt. Als ich es innerhalb von a NSOperationaufrief, erwartete ich, dass es in einem anderen Thread (Not MainThread) als dem ausgeführt wird NSOperation. Kann jemand bitte dieses Szenario im Umgang mit NSOperationund erklären NSOperationQueue.

Zach
quelle
3
Wenn Sie der Hauptwarteschlange Vorgänge hinzufügen, werden diese im Hauptthread ausgeführt. Wenn Sie Ihre eigene NSOperationQueue erstellen und Operationen hinzufügen, werden diese in Threads dieser Warteschlange ausgeführt.
Cy-4AH
1
Ich glaube nicht, dass Sie eine bessere Antwort erhalten werden als @ Cy-4AH, es sei denn, Sie erhalten einen genaueren Code. Ich werde sagen, dass Sie immer einen Haltepunkt in den Code setzen können, und wenn er auslöst, zeigt er Ihnen, in welchem ​​Thread sich die Ablaufverfolgung befindet.
Brad Allred
Was bedeutet "Der Delegierte innerhalb der NSOperation erhält die Daten." bedeuten? Weder NSOperationnoch NSOperationQueuedelegierte Eigenschaften enthalten.
Jeffery Thomas
Sie können Ihren Delegatenaufruf auch auf den Haupt-Thread übertragen, anstatt Annahmen über den aktuellen Thread zu treffen ...
Wain

Antworten:

174

Meine Frage ist, ob der Datenbankschreibvorgang im Hauptthread oder in einem Hintergrundthread erfolgt.

Wenn Sie eine NSOperationQueuevon Grund auf neu erstellen wie in:

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

Es wird in einem Hintergrund-Thread sein:

Operationswarteschlangen stellen normalerweise die Threads bereit, die zum Ausführen ihrer Operationen verwendet werden. In OS X 10.6 und höher verwenden Operationswarteschlangen die libdispatch-Bibliothek (auch als Grand Central Dispatch bezeichnet), um die Ausführung ihrer Operationen zu initiieren. Infolgedessen werden Operationen immer in einem separaten Thread ausgeführt , unabhängig davon, ob sie als gleichzeitige oder nicht gleichzeitige Operationen bezeichnet werden

Es sei denn, Sie verwenden mainQueue:

NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];

Sie können auch Code wie diesen sehen:

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

   // Background work

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

Und die GCD-Version:

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

NSOperationQueuegibt feinere Kontrolle mit dem, was Sie tun möchten. Sie können Abhängigkeiten zwischen den beiden Vorgängen erstellen (herunterladen und in der Datenbank speichern). Um die Daten zwischen einem Block und dem anderen zu übertragen, können Sie beispielsweise davon ausgehen, dass a NSDatavom Server kommt, also:

__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];

Bearbeiten: Warum ich eine __weakReferenz für die Operationen verwende, finden Sie hier . Kurz gesagt, es ist jedoch zu vermeiden, Haltezyklen beizubehalten.

Rui Peres
quelle
3
Die Antwort ist korrekt, aber bitte beachten Sie, dass die richtige Codezeile lautet: NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];und dass die NSOperationQueue im Hauptthread nicht angehalten werden kann.
Gianluca P.
2
@ jacky-boy Aus Interesse, warum die Verwendung der schwachen Verweise auf downloadOperation und saveToDataBaseOperation im endgültigen Code-Snippet?
Michael Wasserfall
RuiAAPeres, hey, ich bin gespannt, warum im endgültigen Code-Snippet schwache Referenzen verwendet werden. Könnten Sie bitte etwas Licht ins Dunkel bringen?
Pavan
@Pavan Die Idee ist, dass Sie einen Aufbewahrungszyklus erhalten, wenn Sie zufällig auf dieselbe Blockoperation in einem eigenen Block verweisen. Als Referenz: conradstoll.com/blog/2013/1/19/…
Rui Peres
Welcher Block ist also derselbe wie der Block, der darin ausgeführt wird?
Pavan
16

Wenn Sie den Datenbankschreibvorgang im Hintergrundthread ausführen möchten, müssen Sie einen NSManagedObjectContextfür diesen Thread erstellen .

Sie können den Hintergrund NSManagedObjectContextin der Startmethode Ihrer jeweiligen NSOperationUnterklasse erstellen .

Überprüfen Sie die Apple-Dokumente auf Parallelität mit Kerndaten.

Sie können auch eine erstellen NSManagedObjectContext, die Anforderungen in einem eigenen Hintergrundthread ausführt, indem Sie sie mit NSPrivateQueueConcurrencyTypeihrer performBlock:Methode erstellen und die Anforderungen innerhalb ihrer Methode ausführen .

serrrgi
quelle
17
Was lässt Sie denken, dass diese Frage mit Kerndaten zusammenhängt?
Nikolai Ruhe
11

Aus NSOperationQueue

In iOS 4 und höher verwenden Operationswarteschlangen Grand Central Dispatch, um Operationen auszuführen. Vor iOS 4 erstellen sie separate Threads für nicht gleichzeitige Vorgänge und starten gleichzeitige Vorgänge über den aktuellen Thread.

Damit,

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

In Ihrem Fall möchten Sie möglicherweise Ihren eigenen "Datenbank-Thread" erstellen, indem Sie eine Unterklasse erstellen NSThreadund Nachrichten an diesen senden performSelector:onThread:.

ilya n.
quelle
Vielen Dank ... u rette mein Leben: [NSOperationQueue new] // nach iOS4, garantiert nicht der Haupt-Thread
ikanimo
9

Der Ausführungsthread von NSOperation hängt davon ab, NSOperationQueuewo Sie die Operation hinzugefügt haben. Achten Sie auf diese Aussage in Ihrem Code -

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

All dies setzt voraus, dass Sie kein weiteres Threading durchgeführt haben main, bei NSOperationdem es sich um das eigentliche Monster handelt, in das die Arbeitsanweisungen geschrieben wurden (voraussichtlich).

Bei gleichzeitigen Vorgängen ist das Szenario jedoch anders. Die Warteschlange kann für jede gleichzeitige Operation einen Thread erzeugen. Obwohl es nicht garantiert ist und von den Systemressourcen im Vergleich zu den Anforderungen an die Betriebsressourcen an diesem Punkt im System abhängt . Sie können die Parallelität der Operationswarteschlange anhand ihrer maxConcurrentOperationCountEigenschaft steuern .

BEARBEITEN -

Ich fand Ihre Frage interessant und habe selbst einige Analysen / Protokolle durchgeführt. Ich habe NSOperationQueue im Hauptthread wie folgt erstellt -

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

Anschließend erstellte ich eine NSOperation und fügte sie mit addOperation hinzu. In der Hauptmethode dieser Operation, als ich nach dem aktuellen Thread gesucht habe,

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

Es war nicht als Hauptthema. Und festgestellt, dass das aktuelle Thread-Objekt kein Haupt-Thread-Objekt ist.

Das benutzerdefinierte Erstellen einer Warteschlange im Hauptthread (ohne Parallelität zwischen den Vorgängen) bedeutet also nicht unbedingt, dass die Vorgänge seriell im Hauptthread selbst ausgeführt werden.

Ashok
quelle
Ich glaube, die Verwendung stellt [[NSOperationQueue alloc] init]sicher, dass die Warteschlange eine zugrunde liegende globale DispatchQueue mit Standardhintergrund verwendet. Daher wird jede benutzerdefinierte initialisierte OperationQueue ihre Aufgaben in DispatchQueues im Hintergrund einspeisen und Aufgaben daher in Threads einspeisen, die nicht der Hauptthread sind. Um Operationen in den Hauptthread einzuspeisen, müssten Sie die Hauptoperationswarteschlange mit so etwas wie erhalten [NSOperationQueue mainQueue]. Dadurch wird die Hauptoperationswarteschlange abgerufen, die die Haupt-DispatchQueue intern verwendet, und daher schließlich Aufgaben an den Hauptthread weitergeleitet.
Gordonium
1

Die Zusammenfassung aus den Dokumenten lautet operations are always executed on a separate thread(nach iOS 4 impliziert GCD zugrunde liegende Operationswarteschlangen).

Es ist trivial zu überprüfen, ob es tatsächlich auf einem Nicht-Haupt-Thread ausgeführt wird:

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

Wenn Sie in einem Thread ausgeführt werden, ist es trivial, GCD / libdispatch zu verwenden, um etwas auf dem Hauptthread auszuführen, unabhängig davon, ob Kerndaten, Benutzeroberfläche oder anderer Code für die Ausführung auf dem Hauptthread erforderlich sind:

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

Wenn Sie nicht triviales Threading durchführen, sollten Sie FMDatabaseQueue verwenden .

Stechpalme
quelle
1
In der Frage wird keine bestimmte Datenbank wie fmdb oder sqlite erwähnt.
Nikolai Ruhe
Das ist richtig, ich habe eine kontextbasierte Annahme gemacht. Wenn die App eine Verbindung zu einer threadsicheren Datenbank mit mehreren Mandanten wie MySQL herstellen würde, wäre die Frage nicht sinnvoll. Es gibt andere Einzelbenutzerdatenbanken, die verwendet werden können, aber SQLite ist bei weitem die am häufigsten verwendete.
Holly