
Dies ist die Fortsetzung der Concurrency in Swift-Serie. Siehe Teil 1 und Teil 2, um die Grundlagen zu verstehen
In diesem Teil werden die folgenden Themen behandelt
- Was sind Operationen und ihre Lebenszustände
- Erstellen Sie einen Block, NSInvocationOperation und benutzerdefinierte Operationen, um Aufgaben asynchron auszuführen
- Abbrechen von Vorgängen
- Was sind Operationswarteschlangen
- So fügen Sie Vorgänge in Vorgangswarteschlangen hinzu
- So erstellen Sie Abhängigkeiten zwischen Vorgängen
- Vorteile von Vorgangswarteschlangen gegenüber GCD
- Versandgruppenimplementierung mithilfe von Vorgangswarteschlangen
Operationen sind ein objektorientierte Methode zum Kapseln von Arbeiten, die Sie asynchron ausführen möchten.
Ein Operationsobjekt ist eine Instanz der Operation or NSOperation
-Klasse (im Foundation framework), mit der Sie die Arbeit kapseln, die Ihre Anwendung ausführen soll.An operation object is an instance of the Operation or NSOperation
class (in the Foundation framework)that you use to encapsulate work you want your application to perform.
Die Operationsklasse selbst ist eine abstrakte Basisklasse, die in Unterklassen unterteilt werden muss, um nützliche Arbeit zu leisten. Obwohl diese Klasse abstrakt ist, bietet sie eine erhebliche Infrastruktur, um den Arbeitsaufwand in Ihren eigenen Unterklassen zu minimieren. Darüber hinaus bietet das Foundation Framework zwei konkrete Unterklassen, die Sie unverändert mit Ihrem vorhandenen Code verwenden können.
Operations State
Eine Operation hat eine Zustandsmaschine, die ihren Lebenszyklus darstellt. Es gibt mehrere mögliche Zustände, die an verschiedenen Stellen dieses Lebenszyklus auftreten:
- Wenn es
instantiated
ist, wechselt es in den ZustandisReady
. - Wenn wir die
start
-Methode aufgerufen haben, wechselt sie in den ZustandisExecuting
. - Wenn die Aufgabe
finished
ist, wechselt sie zuisFinished
- Wenn die Aufgabe ausgeführt wird und Sie
cancel
aufrufen, wechselt sie in den ZustandisCancelled
, bevor Sie in den ZustandisFinished
übergeht
Es gibt hauptsächlich drei Möglichkeiten, Operationen zu erstellen
BlockOperation (Konkrete Klasse)
Eine Klasse, die Sie verwenden, um ein oder mehrere Blockobjekte gleichzeitig auszuführen. Da es mehr als einen Block ausführen kann, arbeitet ein Blockoperationsobjekt mit einer Gruppensemantik; erst wenn alle zugehörigen Blöcke die Ausführung beendet haben, gilt die Operation selbst als abgeschlossen.. Im Blockbetrieb können Sie Operationsabhängigkeiten, KVO, Benachrichtigungen und Stornierungen nutzen.
Wie in Abbildung 1 gezeigt, haben wir diesen Code async
ausgeführt, was bedeutet, dass er sofort zurückkehrt, aber die schlechte Nachricht ist, dass er den Hauptthread blockiert, da operation.start()
im Hauptthread aufgerufen wurde
Operationsobjekte werden standardmäßig synchron ausgeführt — das heißt, sie führen ihre Aufgabe in dem Thread aus, der ihre
start
-Methode aufruft.

Was zum Teufel ist synchron und führen Sie ein oder mehrere Blockobjekte gleichzeitig aus.
Wie in Abbildung 1.0.1 gezeigt, wie Sie sehen können, werden die Aufgaben / Blöcke, die der Blockoperation selbst hinzugefügt wurden, gleichzeitig ausgeführt, aber der Block läuft synchron, was bedeutet, dass er den Thread blockiert, bei dem start aufgerufen wird In unserem Fall ist es Hauptthread

Wie in Abbildung 1.0.2 gezeigt, wird dieser Thread blockiert, da wir die Start-Methode für einen anderen Thread aufrufen

Wie in Abbildung 1.0.3 gezeigt, können wir auch einen Vervollständigungsblock hinzufügen, der aufruft, wenn alle gleichzeitigen Blöcke ausgeführt werden

Führen Sie die Blockoperation gleichzeitig aus
Wie in Abbildung 1 gezeigt.1 da wir die Methode start()
in einem Hintergrundthread aufrufen, führt sie ihre Aufgabe im Thread aus. Es gibt eine coole Möglichkeit, dies mithilfe der Operation Queue zu tun, und wir werden dies später sehen.

NSInvocationOperation (Konkrete Klasse)
Eine Klasse, die Sie verwenden, um ein Operationsobjekt basierend auf einem Objekt und Selektor aus Ihrer Anwendung zu erstellen.
In Objective C können wir NSInvocationOperation
erstellen, während es in Swift nicht verfügbar ist.

3. Benutzerdefinierte Operationen
Die Unterklasse
Operation
gibt Ihnen die vollständige Kontrolle über die Implementierung Ihrer eigenen Operationen, einschließlich der Möglichkeit, die Standardausführung Ihrer Operation zu ändern und ihren Status zu melden.
Wie in Abbildung 2 gezeigt, haben wir benutzerdefinierte Operationen erstellt, indem wir sie von der Basisklasse Operation
unterklassifiziert und ihre Methode main
überschrieben haben. Wenn Sie eine Unterklasse erstellen, setzen Sie Ihre Aufgabe auf die Methode main
. Wir haben eine nicht gleichzeitige benutzerdefinierte Operation implementiert und in diesem Fall den Hauptthread blockiert

Wenn Sie vorhaben, Vorgänge manuell auszuführen und sie dennoch asynchron ausführen möchten, müssen Sie die entsprechenden Maßnahmen ergreifen, um dies sicherzustellen. Dazu definieren Sie Ihr Operationsobjekt als gleichzeitige Operation.
Wie in Abbildung 3 gezeigt, haben wir die folgenden Schritte ausgeführt, um die Aufgabe gleichzeitig auszuführen
- Erstellte Unterklasse
MyConcurrentQueue
. Tippfehler: Der Name sollte seinMyConcurrentOperations
- Aufruf
start()
Methode ruftmain()
Methode auf Hintergrundthread - Bei der Hauptmethode haben wir unsere Aufgabe definiert und eine Sache zu beachten Wir können auch den Fall abbrechen
- Beim Aufruf von
cancel
Bei der benutzerdefinierten Operation wird in den ZustandisCancelled
übergegangen und die Schleife unterbrochen, und wie in Abbildung 3 gezeigt, werden nur 39487 Elemente gedruckt

Operation Queues
- Operations Queues sind Cocoas High-Level-Abstraktion auf GCD
- Mit Operation Queues Sie werden die wahre Macht der Operationen sehen, anstatt die Operation selbst zu starten, geben Sie sie der Operation Queue, die dann die Planung und Ausführung übernimmt.
- Vorgangswarteschlangen sind eine objektorientierte Methode zum Kapseln von Arbeiten, die Sie asynchron ausführen möchten.
- Sie fügen
operations
(Aufgaben / Arbeit) zuoperation queue
hinzu und wir haben besprochen, wie wir Operationen mit zwei Methoden erstellen können.
Operationen hinzufügen
Wie in Abbildung 4 gezeigt, haben wir zwei Operationen (mit Block) erstellt und in die Operationswarteschlange eingefügt. Die Operationswarteschlange hat beide Operationen in einem Hintergrundthread gestartet und ausgeführt. Keine Notwendigkeit, start()
Methode auf benutzerdefinierten Thread 🆒 aufzurufen. Wenn wir der Operationswarteschlange eine Operation hinzufügen, wird sie ausgeführt, sobald sie fertig ist

Wie in Abbildung 5 gezeigt, haben wir die Aufgabe nur seriell ausgeführt, oder Sie können sagen, dass wir die serielle Warteschlange mithilfe von Operationswarteschlangen implementiert haben. Bitte beachten Sie meinen Teil 1, wenn Sie nicht wissen, was die serielle Warteschlange ist, indem Sie Folgendes festlegen maxConcurrentOperationCount
= 1
maxConcurrentOperationCount →
Die maximale Anzahl von Operationen in der Warteschlange, die gleichzeitig ausgeführt werden können. Der Standardwert ist -1, was bedeutet, dass das System entscheiden soll

Durch Setzen von maxConcurrentOperationCount
= 2 haben wir eine gleichzeitige Warteschlange erstellt und jetzt werden Aufgaben gleichzeitig ausgeführt, wie in Abbildung gezeigt 6

Operationsabhängigkeiten
Wie in Abbildung 7 gezeigt, haben wir erneut eine serielle Warteschlange erstellt, indem wir Abhängigkeiten zwischen zwei Aufgaben hinzugefügt haben. Wir haben zwei Blockoperationen erstellt und sagen, dass Task 1 erst gestartet wird, wenn Task 2 durch Aufrufen beendet ist blockOperations1.addDependency(blockOperations2)

Versandgruppenimplementierung mit Operations Queue
In Teil 2 haben wir die GCD-Versandgruppenfunktion verwendet, um einen Thread zu blockieren, bis eine oder mehrere Aufgaben ausgeführt wurden. Wie in Abbildung 8 gezeigt, haben wir das gleiche Verhalten mithilfe von Operationswarteschlangen mithilfe von Abhängigkeiten implementiert. Dies ist sehr hilfreich, wenn Sie erst dann Fortschritte erzielen können, wenn alle angegebenen Aufgaben abgeschlossen sind.
Wie in Abbildung 8 gezeigt, haben wir drei Aufgaben und wir wollten gleichzeitig ausführen und wenn alle Aufgaben abgeschlossen sind, müssen wir eine Methode aufrufen, um anzuzeigen, dass alle Aufgaben abgeschlossen sind und was wir getan haben
- Erstellt eine Operationswarteschlange
- Erstellt drei Blockoperationen, die Aufgaben ausführen
- Erstellt eine Abschlussblockoperation (blockOperations4), die ausgelöst wird, wenn alle drei Aufgaben abgeschlossen sind
- Machte
blockOperations4
abhängig vonblockOperations1
,blockOperations2
undblockOperations3
was bedeutet, dass blockOperations4 ausgeführt wird, wenn alle drei Aufgaben abgeschlossen sind -
waitUntilFinished
→ Blockiert die Ausführung des aktuellen Threads, bis das Operationsobjekt seine Aufgabe beendet da wir den aktuellen Thread, der main ist, nicht blockieren möchten, weisen wir ihn mit false - Führen Sie diesen Code aus und „Alle Operationen sind abgeschlossen“ wird gedruckt, wenn task1, task2 und task3 beendet sind

Wie in Abbildung 9 gezeigt, blockieren wir einfach den Hauptthread, indem wir waitUntilFinished = true.
Die Frage ist also, wann es hilfreich ist, und Sie erhalten die Antwort im nächsten Abschnitt

Wie in Abbildung 10 gezeigt, haben wir ein Dispatch-Gruppenverhalten mit der Operation Queue implementiert, ohne Abhängigkeiten zu verwenden, was wir getan haben Wir haben die Funktion waitUntilFinished
entsprechend verwendet . Wenn Sie sich im Hintergrundthread befinden, können Sie diesen Thread blockieren, um dieses Verhalten zu erreichen. Ich habe absichtlich mit der DispatchQueue.global().async
-Methode zum Hintergrundthread gewechselt, siehe Teil 1, um diesen Code zu verstehen
Wir haben der Operationswarteschlange mitgeteilt, Task 1, Task 2 und Task 3 in der Operationswarteschlange auszuführen und den aktuellen Thread zu blockieren, bis diese gleichzeitigen Tasks ihre Ausführung beenden

Vorteile von Operation Queues über GCD
- Die Operation API bietet Unterstützung für Abhängigkeiten. Sie können komplexe Abhängigkeiten zwischen Aufgaben sehr einfach erstellen, obwohl Sie dies in GCD erreichen können, aber Sie müssen viel Arbeit leisten.
- Die Klassen NSOperation und NSOperationQueue verfügen über eine Reihe von Eigenschaften, die mithilfe von KVO (Key Value Observing) beobachtet werden können. Dies ist ein weiterer wichtiger Vorteil, wenn Sie den Status eines Vorgangs oder einer Vorgangswarteschlange überwachen möchten.
- Vorgänge können angehalten, fortgesetzt und abgebrochen werden. Sobald Sie eine Aufgabe mit Grand Central Dispatch versenden, haben Sie keine Kontrolle mehr über die Ausführung dieser Aufgabe. Die NSOperation API ist in dieser Hinsicht flexibler und gibt dem Entwickler die Kontrolle über den Lebenszyklus der Operation
- Die NSOperationQueue fügt dem Mix auch eine Reihe von Vorteilen hinzu. Sie können beispielsweise die maximale Anzahl von Operationen in der Warteschlange angeben, die gleichzeitig ausgeführt werden können. Auf diese Weise können Sie einfach steuern, wie viele Vorgänge gleichzeitig ausgeführt werden, oder eine Warteschlange für serielle Vorgänge erstellen.
Demnächst
Im nächsten Teil werden wir uns den tatsächlichen Anwendungsfall der Erstellung einer benutzerdefinierten Operation ansehen
Nützliche Links
· https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationObjects/OperationObjects.html#//apple_ref/doc/uid/TP40008091-CH101-SW1