webentwicklung-frage-antwort-db.com.de

Parallele serielle Warteschlangen in GCD

Ich habe Schwierigkeiten, die gleichzeitigen und seriellen Warteschlangen in GCD vollständig zu verstehen. Ich habe einige Probleme und hoffe, dass mir jemand klar und auf den Punkt antworten kann.

  1. Ich lese gerade, dass serielle Warteschlangen erstellt und verwendet werden, um Aufgaben nacheinander auszuführen. Was passiert jedoch, wenn:

    • Ich erstelle eine serielle Warteschlange
    • Ich benutze dispatch_async (in der gerade erstellten seriellen Warteschlange) dreimal, um drei Blöcke A, B, C zu versenden

    Werden die drei Blöcke ausgeführt:

    • in Reihenfolge A, B, C, da die Warteschlange seriell ist

      OR

    • gleichzeitig (zur gleichen Zeit auf Parralel-Threads), weil ich ASYNC-Dispatch verwendet habe
  2. Ich lese, dass ich dispatch_sync für gleichzeitige Warteschlangen verwenden kann, um Blöcke nacheinander auszuführen. WARUM gibt es dann überhaupt serielle Warteschlangen, da ich immer eine gleichzeitige Warteschlange verwenden kann, in der ich SYNCHRONOUS so viele Blöcke senden kann, wie ich möchte?

    Danke für jede gute Erklärung!

84

Ein einfaches Beispiel: Sie haben einen Block, dessen Ausführung eine Minute dauert. Sie fügen es aus dem Haupt-Thread zu einer Warteschlange hinzu. Schauen wir uns die vier Fälle an.

  • async - concurrent: Der Code wird in einem Hintergrundthread ausgeführt. Die Steuerung kehrt sofort zum Hauptthread (und zur Benutzeroberfläche) zurück. Der Block kann nicht davon ausgehen, dass es der einzige Block ist, der in dieser Warteschlange ausgeführt wird
  • async - serial: Der Code wird auf einem Hintergrundthread ausgeführt. Die Steuerung kehrt sofort zum Hauptthread zurück. Der Block kann kann davon ausgehen, dass es der einzige Block ist, der in dieser Warteschlange ausgeführt wird
  • sync - concurrent: Der Code wird in einem Hintergrundthread ausgeführt, der Hauptthread wartet jedoch darauf, dass er beendet wird, und blockiert Aktualisierungen der Benutzeroberfläche. Der Block kann nicht davon ausgehen, dass es der einzige Block ist, der in dieser Warteschlange ausgeführt wird (ich hätte vor ein paar Sekunden einen weiteren Block mit Async hinzufügen können.)
  • sync - serial: Der Code wird in einem Hintergrundthread ausgeführt, der Hauptthread wartet jedoch auf den Abschluss und blockiert alle Aktualisierungen der Benutzeroberfläche. Der Block kann kann davon ausgehen, dass es der einzige Block ist, der in dieser Warteschlange ausgeführt wird

Offensichtlich würden Sie keines der letzten beiden für lang laufende Prozesse verwenden. Normalerweise sehen Sie es, wenn Sie versuchen, die Benutzeroberfläche (immer im Haupt-Thread) von etwas zu aktualisieren, das möglicherweise in einem anderen Thread ausgeführt wird.

165

Hier sind ein paar Experimente, die ich gemacht habe, um mich über diese serial, concurrent Warteschlangen mit Grand Central Dispatch zu informieren.

 func doLongAsyncTaskInSerialQueue() {

   let serialQueue = DispatchQueue(label: "com.queue.Serial")
      for i in 1...5 {
        serialQueue.async {

            if Thread.isMainThread{
                print("task running in main thread")
            }else{
                print("task running in background thread")
            }
            let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
            let _ = try! Data(contentsOf: imgURL)
            print("\(i) completed downloading")
        }
    }
}

Task wird in einem anderen Thread als dem Hauptthread ausgeführt, wenn Sie asynchron in GCD verwenden. Async bedeutet, dass die nächste Zeile ausgeführt wird. Warten Sie nicht, bis der Block ausgeführt wird. Dies führt dazu, dass der Hauptthread und die Hauptwarteschlange nicht blockiert werden. Seit ihrer seriellen Warteschlange werden alle in der Reihenfolge ausgeführt, in der sie der seriellen Warteschlange hinzugefügt werden. Aufgaben, die seriell ausgeführt werden, werden immer einzeln von dem einzelnen, der Warteschlange zugeordneten Thread ausgeführt.

func doLongSyncTaskInSerialQueue() {
    let serialQueue = DispatchQueue(label: "com.queue.Serial")
    for i in 1...5 {
        serialQueue.sync {
            if Thread.isMainThread{
                print("task running in main thread")
            }else{
                print("task running in background thread")
            }
            let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
            let _ = try! Data(contentsOf: imgURL)
            print("\(i) completed downloading")
        }
    }
}

Task kann im Hauptthread ausgeführt werden, wenn Sie die Synchronisierung in GCD verwenden. Sync führt einen Block in einer bestimmten Warteschlange aus und wartet, bis diese abgeschlossen ist. Dies führt zum Blockieren des Hauptthreads oder der Hauptwarteschlange. Da die Hauptwarteschlange warten muss, bis der übergebene Block abgeschlossen ist, ist der Hauptthread für die Verarbeitung von Blöcken aus anderen Warteschlangen verfügbar Hauptwarteschlange. Daher besteht die Möglichkeit, dass der Code, der in der Hintergrundwarteschlange ausgeführt wird, tatsächlich im Hauptthread ausgeführt wird Seit ihrer seriellen Warteschlange werden alle in der Reihenfolge ausgeführt, in der sie hinzugefügt werden (FIFO).

func doLongASyncTaskInConcurrentQueue() {
    let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent)
    for i in 1...5 {
        concurrentQueue.async {
            if Thread.isMainThread{
                print("task running in main thread")
            }else{
                print("task running in background thread")
            }
            let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
            let _ = try! Data(contentsOf: imgURL)
            print("\(i) completed downloading")
        }
        print("\(i) executing")
    }
}

Task wird im Hintergrundthread ausgeführt, wenn Sie asynchron in GCD verwenden. Async bedeutet, dass die nächste Zeile ausgeführt wird. Warten Sie nicht, bis der Block ausgeführt wird, was zu einem nicht blockierenden Haupt-Thread führt. Denken Sie daran, dass die Task in der gleichzeitigen Warteschlange in der Reihenfolge abgearbeitet wird, in der sie der Warteschlange hinzugefügt werden, wobei jedoch unterschiedliche Threads an die .__ angehängt sind. Warteschlange. Denken Sie daran, dass sie die Aufgabe nicht als Auftrag abschließen sollen Sie werden der Warteschlange hinzugefügt. Die Reihenfolge der Aufgaben unterscheidet sich jedes Mal Threads werden zwangsläufig automatisch erstellt. Aufgaben werden parallel ausgeführt. Mit mehr als Wenn (maxConcurrentOperationCount) erreicht ist, verhalten sich einige Aufgaben als serielle bis ein Thread frei ist.

func doLongSyncTaskInConcurrentQueue() {
  let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent)
    for i in 1...5 {
        concurrentQueue.sync {
            if Thread.isMainThread{
                print("task running in main thread")
            }else{
                print("task running in background thread")
            }
            let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
            let _ = try! Data(contentsOf: imgURL)
            print("\(i) completed downloading")
        }
        print("\(i) executed")
    }
}

Task kann im Hauptthread ausgeführt werden, wenn Sie die Synchronisierung in GCD verwenden. Sync führt einen Block in einer bestimmten Warteschlange aus und wartet, bis diese abgeschlossen ist. Dies führt zum Blockieren des Hauptthreads oder der Hauptwarteschlange. Da die Hauptwarteschlange warten muss, bis der übergebene Block abgeschlossen ist, ist der Hauptthread für die Verarbeitung von Blöcken aus anderen Warteschlangen verfügbar Hauptwarteschlange. Daher besteht die Möglichkeit, dass der Code, der in der Hintergrundwarteschlange ausgeführt wird, tatsächlich im Hauptthread ausgeführt wird. Da die gleichzeitige Warteschlange die Aufgaben nicht in der Reihenfolge beendet, in der sie der Warteschlange hinzugefügt werden. Bei synchronem Betrieb ist dies jedoch der Fall, obwohl sie möglicherweise von verschiedenen Threads verarbeitet werden. Es verhält sich also wie dies die serielle Warteschlange ist.

Hier ist eine Zusammenfassung dieser Experimente

Denken Sie daran, GCD zu verwenden, Sie fügen nur Aufgaben zur Warteschlange hinzu und führen Aufgaben aus dieser Warteschlange aus. In der Warteschlange wird Ihre Aufgabe entweder im Haupt- oder Hintergrundthread abgesetzt, je nachdem, ob der Vorgang synchron oder asynchron ist. Warteschlangen sind seriell, gleichzeitig, Hauptverteilungswarteschlange. Alle Aufgaben, die Sie ausführen, werden standardmäßig über die Hauptverteilungswarteschlange ausgeführt. Es gibt bereits vier vordefinierte globale gleichzeitige Warteschlangen, die Ihre Anwendung verwenden kann, und eine Hauptwarteschlange (DispatchQueue.main) .You Sie können auch Ihre eigene Warteschlange manuell erstellen und Aufgaben aus dieser Warteschlange ausführen.

UI-bezogene Aufgabe sollte immer vom Haupt-Thread aus ausgeführt werden, indem die Aufgabe in die Hauptwarteschlange eingereiht wird. Das Dienstprogramm für die Kurzbezeichnung ist DispatchQueue.main.sync/async, während netzwerkbezogene/umfangreiche Vorgänge immer asynchron ausgeführt werden sollten (egal, in welchem ​​Thread Sie Haupt- oder Hintergrund verwenden

EDIT: Es gibt jedoch Fälle, in denen Sie Netzwerkaufrufe synchron in einem Hintergrundthread ausführen müssen, ohne die Benutzeroberfläche einzufrieren (z. B. OAuth-Token umfrischen und warten, wenn es erfolgreich ist oder nicht). Sie müssen diese Methode asynchron einschließen Operation. Auf diese Weise werden Ihre schweren Operationen in der Reihenfolge und ohne Blockieren des Hauptthreads ausgeführt.

func doMultipleSyncTaskWithinAsynchronousOperation() {
    let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent)
    concurrentQueue.async {
        let concurrentQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.default)
        for i in 1...5 {
            concurrentQueue.sync {
                let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
                let _ = try! Data(contentsOf: imgURL)
                print("\(i) completed downloading")
            }
            print("\(i) executed")
        }
    }
}

EDIT EDIT: Sie können Demo-Videos hier anschauen.

95
LC 웃

Zunächst ist es wichtig, den Unterschied zwischen Threads und Warteschlangen zu kennen und zu wissen, was GCD wirklich tut. Wenn wir Dispatch-Warteschlangen (über GCD) verwenden, stehen wir wirklich in der Warteschlange, nicht im Threading. Das Dispatch-Framework wurde speziell entwickelt, um uns vom Threading abzuhalten, da Apple zugibt, dass die Implementierung einer korrekten Threading-Lösung extrem schwierig, wenn nicht [manchmal] unmöglich wird. " Um Aufgaben gleichzeitig auszuführen (Aufgaben, bei denen die Benutzeroberfläche nicht eingefroren werden soll), müssen Sie lediglich eine Warteschlange mit diesen Aufgaben erstellen und an GCD übergeben. Und GCD übernimmt das gesamte zugehörige Threading. Deshalb müssen wir uns nur anstellen.

Das zweite, was Sie sofort wissen müssen, ist, was eine Aufgabe ist. Eine Aufgabe ist der gesamte Code in diesem Warteschlangenblock (nicht in der Warteschlange, da wir einer Warteschlange jederzeit Dinge hinzufügen können, sondern in dem Bereich, in dem wir sie der Warteschlange hinzugefügt haben). Eine Aufgabe wird manchmal als Block bezeichnet, und ein Block wird manchmal als Aufgabe bezeichnet (sie werden jedoch häufiger als Aufgaben bezeichnet, insbesondere in der Swift Community). Unabhängig davon, wie viel oder wenig Code vorhanden ist, wird der gesamte Code in geschweiften Klammern als eine einzige Aufgabe betrachtet:

serialQueue.async {
    // this is one task
    // it can be any number of lines with any number of methods
}
serialQueue.async {
    // this is another task added to the same queue
    // this queue now has two tasks
}

Es gibt zwei Arten von Warteschlangen, serielle und gleichzeitige, aber alle Warteschlangen sind relativ zueinander gleichzeitig. Die Tatsache, dass Sie Code "im Hintergrund" ausführen möchten, bedeutet, dass Sie ihn gleichzeitig mit einem anderen Thread (normalerweise dem Hauptthread) ausführen möchten. Daher führen alle seriellen oder gleichzeitigen Versandwarteschlangen ihre Aufgaben gleichzeitig aus im Vergleich zu anderen Warteschlangen. Jede von Warteschlangen ausgeführte Serialisierung (von seriellen Warteschlangen) hat nur mit den Aufgaben in dieser einzelnen [seriellen] Dispatch-Warteschlange zu tun (wie im obigen Beispiel, in dem sich zwei Aufgaben in derselben seriellen Warteschlange befinden). Diese Aufgaben werden nacheinander ausgeführt der andere, niemals gleichzeitig).

Serielle Warteschlangen (häufig als private Versandwarteschlangen bezeichnet) gewährleisten die Ausführung von Aufgaben nacheinander von Anfang bis Ende in der Reihenfolge, in der sie hinzugefügt wurden bestimmte Warteschlange. Dies ist die einzige Garantie für die Serialisierung in der Diskussion über Versandwarteschlangen - dass die spezifischen Aufgaben in einer spezifischen seriellen Warteschlange seriell ausgeführt werden. Serielle Warteschlangen können jedoch gleichzeitig mit anderen seriellen Warteschlangen ausgeführt werden, wenn es sich um separate Warteschlangen handelt, da wiederum alle Warteschlangen relativ zueinander gleichzeitig ausgeführt werden. Alle Aufgaben werden auf unterschiedlichen Threads ausgeführt, aber nicht jede Aufgabe wird garantiert auf demselben Thread ausgeführt (nicht wichtig, aber interessant zu wissen). Und das iOS-Framework enthält keine gebrauchsfertigen seriellen Warteschlangen. Sie müssen sie erstellen. Private (nicht globale) Warteschlangen sind standardmäßig seriell. So erstellen Sie eine serielle Warteschlange:

let serialQueue = DispatchQueue(label: "serial")

Sie können es über die Eigenschaft attribute gleichzeitig ausführen:

let concurrentQueue = DispatchQueue(label: "concurrent", attributes: [.concurrent])

Wenn Sie der privaten Warteschlange jedoch noch keine anderen Attribute hinzufügen, empfiehlt Apple, nur eine der sofort einsatzbereiten globalen Warteschlangen zu verwenden (die alle gleichzeitig aktiv sind).

Gleichzeitige Warteschlangen (häufig als globale Versandwarteschlangen bezeichnet) können Aufgaben gleichzeitig ausführen. Es ist jedoch gewährleistet, dass die Tasks in der Reihenfolge, in der sie dieser bestimmten Warteschlange hinzugefügt wurden, initiieren abgeschlossen werden. Im Gegensatz zu seriellen Warteschlangen wartet die Warteschlange jedoch nicht darauf, dass die erste Task beendet wird, bevor die zweite gestartet wird Aufgabe. Aufgaben (wie bei seriellen Warteschlangen) werden auf unterschiedlichen Threads ausgeführt, und (wie bei seriellen Warteschlangen) wird nicht garantiert, dass jede Aufgabe auf demselben Thread ausgeführt wird (nicht wichtig, aber interessant zu wissen). Das iOS-Framework wird mit vier sofort einsatzbereiten Warteschlangen geliefert. Sie können anhand des obigen Beispiels eine gleichzeitige Warteschlange erstellen oder einfach eine der globalen Warteschlangen von Apple verwenden (was normalerweise empfohlen wird):

let concurrentQueue = DispatchQueue.global(qos: .default)

RETAIN-CYCLE RESISTANT: Dispatch-Warteschlangen sind Objekte mit Referenzzählung, aber Sie müssen globale Warteschlangen nicht beibehalten und freigeben, da sie global sind. Daher werden Aufbewahrung und Freigabe ignoriert. Sie können direkt auf globale Warteschlangen zugreifen, ohne sie einer Eigenschaft zuweisen zu müssen.

Es gibt zwei Möglichkeiten, Warteschlangen zu versenden: synchron und asynchron.

Sync Dispatching bedeutet, dass der Thread, an den die Warteschlange gesendet wurde (der aufrufende Thread), nach dem Senden der Warteschlange angehalten wird und auf den Abschluss der Task in diesem Warteschlangenblock wartet Ausführung vor der Wiederaufnahme. So versenden Sie synchron:

DispatchQueue.global(qos: .default).sync {
    // task goes in here
}

Asynchrone Weiterleitung bedeutet, dass der aufrufende Thread nach der Weiterleitung der Warteschlange weiter ausgeführt wird und nicht darauf wartet, dass die Task in diesem Warteschlangenblock die Ausführung beendet. So versenden Sie asynchron:

DispatchQueue.global(qos: .default).async {
    // task goes in here
}

Nun könnte man meinen, um eine Aufgabe seriell auszuführen, sollte eine serielle Warteschlange verwendet werden, und das ist nicht genau richtig. Um mehrere Tasks in serieller Reihenfolge auszuführen, sollte eine serielle Warteschlange verwendet werden, aber alle Tasks (für sich isoliert) werden in serieller Reihenfolge ausgeführt. Betrachten Sie dieses Beispiel:

whichQueueShouldIUse.syncOrAsync {

    for i in 1...10 {
        print(i)
    }
    for i in 1...10 {
        print(i + 100)
    }
    for i in 1...10 {
        print(i + 1000)
    }

}

Unabhängig davon, wie Sie diese Warteschlange konfigurieren (seriell oder gleichzeitig) oder verteilen (synchronisieren oder asynchronisieren), wird diese Aufgabe immer seriell ausgeführt. Die dritte Schleife wird niemals vor der zweiten Schleife ablaufen und die zweite Schleife wird niemals vor der ersten Schleife ablaufen. Dies gilt für jede Warteschlange, die einen Versand ausführt. Es ist, wenn Sie mehrere Aufgaben und/oder Warteschlangen einführen, bei denen Serien- und Nebenläufigkeit wirklich ins Spiel kommen.

Betrachten Sie diese zwei Warteschlangen, eine serielle und eine gleichzeitige:

let serialQueue = DispatchQueue(label: "serial")
let concurrentQueue = DispatchQueue.global(qos: .default)

Angenommen, wir versenden zwei Warteschlangen gleichzeitig asynchron:

concurrentQueue.async {
    for i in 1...5 {
        print(i)
    }
}
concurrentQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}

1
101
2
102
103
3
104
4
105
5

Ihre Ausgabe ist (wie erwartet) durcheinander, es ist jedoch zu beachten, dass jede Warteschlange ihre eigene Aufgabe seriell ausgeführt hat. Dies ist das grundlegendste Beispiel für Parallelität - zwei Aufgaben, die gleichzeitig im Hintergrund in derselben Warteschlange ausgeführt werden. Jetzt machen wir die erste Serie:

serialQueue.async {
    for i in 1...5 {
        print(i)
    }
}
concurrentQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}

101
1
2
102
3
103
4
104
5
105

Soll die erste Warteschlange nicht seriell ausgeführt werden? Es war (und so war das zweite). Was auch immer im Hintergrund passiert ist, kümmert die Warteschlange nicht. Wir sagten der seriellen Warteschlange, sie solle seriell ausgeführt werden, und es geschah ... aber wir gaben ihr nur eine Aufgabe. Geben wir nun zwei Aufgaben:

serialQueue.async {
    for i in 1...5 {
        print(i)
    }
}
serialQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}

1
2
3
4
5
101
102
103
104
105

Und dies ist das grundlegendste (und einzig mögliche) Beispiel für Serialisierung - zwei Tasks, die seriell (nacheinander) im Hintergrund (zum Haupt-Thread) in derselben Warteschlange ausgeführt werden. Wenn wir sie jedoch zu zwei separaten seriellen Warteschlangen gemacht haben (im obigen Beispiel handelt es sich um dieselbe Warteschlange), wird ihre Ausgabe erneut durcheinander gebracht:

serialQueue.async {
    for i in 1...5 {
        print(i)
    }
}
serialQueue2.async {
    for i in 1...5 {
        print(i + 100)
    }
}

1
101
2
102
3
103
4
104
5
105

Und das habe ich gemeint, als ich sagte, dass alle Warteschlangen relativ zueinander gleichzeitig sind. Hierbei handelt es sich um zwei serielle Warteschlangen, die ihre Aufgaben gleichzeitig ausführen (da es sich um separate Warteschlangen handelt). Eine Warteschlange kennt oder kümmert sich nicht um andere Warteschlangen. Gehen wir nun zu zwei seriellen Warteschlangen (derselben Warteschlange) zurück und fügen eine dritte hinzu, eine gleichzeitige:

serialQueue.async {
    for i in 1...5 {
        print(i)
    }
}
serialQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}
concurrentQueue.async {
    for i in 1...5 {
        print(i + 1000)
    }
}

1
2
3
4
5
101
102
103
104
105
1001
1002
1003
1004
1005

Das ist ein bisschen unerwartet. Warum hat die gleichzeitige Warteschlange gewartet, bis die seriellen Warteschlangen beendet sind, bevor sie ausgeführt wurden? Das ist keine Nebenläufigkeit. Ihr Spielplatz zeigt möglicherweise eine andere Ausgabe, meine hat dies jedoch gezeigt. Und das zeigte sich, weil die Priorität meiner Warteschlange nicht hoch genug war, damit GCD ihre Aufgabe früher ausführen konnte. Wenn ich also alles beim Alten halte, aber die QoS der globalen Warteschlange ändere (die Dienstqualität, die einfach die Prioritätsstufe der Warteschlange ist) let concurrentQueue = DispatchQueue.global(qos: .userInteractive), dann ist die Ausgabe wie erwartet:

1
1001
1002
1003
2
1004
1005
3
4
5
101
102
103
104
105

Die beiden seriellen Warteschlangen haben ihre Aufgaben (wie erwartet) seriell ausgeführt, und die gleichzeitige Warteschlange hat ihre Aufgabe schneller ausgeführt, da ihr eine hohe Priorität (hohe Dienstgüte oder Dienstgüte) zugewiesen wurde.

Wie in unserem ersten Druckbeispiel zeigen zwei Warteschlangen gleichzeitig einen durcheinandergebrachten Ausdruck (wie erwartet). Damit sie ordnungsgemäß seriell gedruckt werden können, müssen beide seriellen Warteschlangen erstellt und mit demselben Etikett versehen werden, damit sie dieselbe Warteschlange haben. Dann wird jede Aufgabe in Bezug auf die andere seriell ausgeführt. Eine andere Möglichkeit, sie zum seriellen Drucken zu bringen, besteht darin, beide gleichzeitig zu drucken, aber die Versandmethode zu ändern:

concurrentQueue.sync {
    for i in 1...5 {
        print(i)
    }
}
concurrentQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}

1
2
3
4
5
101
102
103
104
105

Denken Sie daran, dass Synchronisierungs-Dispatching nur bedeutet, dass der aufrufende Thread wartet, bis die Aufgabe in der Warteschlange abgeschlossen ist, bevor er fortfährt. Die Einschränkung besteht hier offensichtlich darin, dass der aufrufende Thread (in diesem Fall der Hauptthread) eingefroren wird, bis die erste Task abgeschlossen ist. Dies kann der Fall sein, wenn Sie möchten, dass die Benutzeroberfläche ausgeführt wird. Dies ist keine alltägliche Lösung für Programmierer, die nach Serialisierung suchen, aber sie hat ihre Verwendungszwecke. Manchmal möchten Sie die Benutzeroberfläche für einen sehr kurzen, oft nicht wahrnehmbaren Moment einfrieren (z. B., wenn Sie verhindern möchten, dass der Benutzer Schaltflächen einfügt mitten in einem wichtigen und kurzen Ereignis). Ich kann noch viele weitere Beispiele durchgehen, aber Sie sollten jetzt schon auf die Idee kommen.

https://developer.Apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html#//Apple_ref/doc/uid/TP40008091-CH1-SW1

21
bsod

Wenn ich richtig verstehe, wie GCD funktioniert, denke ich, gibt es zwei Arten von DispatchQueue, serial und concurrent, gleichzeitig gibt es zwei Möglichkeiten, wie DispatchQueue seine Aufgaben abschickt, der zugewiesene closure, der erste ist async und der andere ist sync. Diese zusammen bestimmen, wie die Schließung (Task) tatsächlich ausgeführt wird.

Ich fand heraus, dass serial und concurrent bedeuten, wie viele Threads die Warteschlange verwenden kann, serial einen, während concurrent viele bedeutet. Und sync und async bedeuten, dass die Task auf dem Thread, dem Thread des Aufrufers oder dem Thread, der dieser Warteschlange zugrunde liegt, ausgeführt wird. sync bedeutet, dass der Aufruf des Threads des Aufrufers ausgeführt wird, wohingegen async für den zugrunde liegenden Thread ausgeführt wird.

Das Folgende ist experimenteller Code, der auf einem Xcode-Spielplatz ausgeführt werden kann.

PlaygroundPage.current.needsIndefiniteExecution = true
let cq = DispatchQueue(label: "concurrent.queue", attributes: .concurrent)
let cq2 = DispatchQueue(label: "concurent.queue2", attributes: .concurrent)
let sq = DispatchQueue(label: "serial.queue")

func codeFragment() {
  print("code Fragment begin")
  print("Task Thread:\(Thread.current.description)")
  let imgURL = URL(string: "http://stackoverflow.com/questions/24058336/how-do-i-run-asynchronous-callbacks-in-playground")!
  let _ = try! Data(contentsOf: imgURL)
  print("code Fragment completed")
}

func serialQueueSync() { sq.sync { codeFragment() } }
func serialQueueAsync() { sq.async { codeFragment() } }
func concurrentQueueSync() { cq2.sync { codeFragment() } }
func concurrentQueueAsync() { cq2.async { codeFragment() } }

func tasksExecution() {
  (1...5).forEach { (_) in
    /// Using an concurrent queue to simulate concurent task executions.
    cq.async {
      print("Caller Thread:\(Thread.current.description)")
      /// Serial Queue Async, tasks run serially, because only one thread that can be used by serial queue, the underlying thread of serial queue.
      //serialQueueAsync()
      /// Serial Queue Sync, tasks run serially, because only one thread that can be used by serial queue,one by one of the callers' threads.
      //serialQueueSync()
      /// Concurrent Queue Async, tasks run concurrently, because tasks can run on different underlying threads
      //concurrentQueueAsync()
      /// Concurrent Queue Sync, tasks run concurrently, because tasks can run on different callers' thread
      //concurrentQueueSync()
    }
  }
}
tasksExecution()

Ich hoffe es kann hilfreich sein.

4
Keith

Ich denke das mit dieser Metapher (Hier ist der Link zum Originalbild):

Dad's gonna need some help

Stellen wir uns vor, Ihr Vater spült und Sie haben gerade ein Glas Soda getrunken. Du bringst das Glas zu deinem Vater, um es aufzuräumen und stellst es neben das andere Gericht.

Jetzt macht dein Vater das Geschirr ganz alleine, also muss er es eins nach dem anderen machen: Dein Vater steht hier für eine serielle Warteschlange .

Aber du bist nicht wirklich daran interessiert, dort zu stehen und zuzusehen, wie es aufgeräumt wird. Lassen Sie das Glas fallen und gehen Sie zurück in Ihr Zimmer. Dies wird als asynchroner Versand bezeichnet. Dein Vater kann oder kann dich nicht wissen lassen, wenn er fertig ist, aber das Wichtigste ist, dass du nicht darauf wartest, dass das Glas gereinigt wird. du gehst zurück in dein Zimmer, weißt du, Kinderkram.

Nehmen wir jetzt an, Sie haben noch Durst und möchten etwas Wasser auf demselben Glas haben, das auch Ihr Favorit ist. Sobald es gereinigt ist, möchten Sie es wirklich wiederhaben. Also stehst du da und siehst deinem Vater beim Abwasch zu, bis du fertig bist. Dies ist ein Synchronisierungsversand , da Sie blockiert sind, während Sie auf die Beendigung der Aufgabe warten.

Und zum Schluss sagen wir, deine Mutter beschließt, deinem Vater zu helfen und mit ihm den Abwasch zu machen. Jetzt wird die Warteschlange zu einer gleichzeitigen Warteschlange , da mehrere Geschirrteile gleichzeitig gereinigt werden können. Beachten Sie jedoch, dass Sie sich immer noch dafür entscheiden können, dort zu warten oder in Ihr Zimmer zurückzukehren, unabhängig davon, wie sie funktionieren.

Hoffe das hilft

2
**1.    I'm reading that serial queues are created and used in order to execute tasks one after the other. However, what happens if:
    •   I create a serial queue
    •   I use dispatch_async (on the serial queue I just created) three times to dispatch three blocks A,B,C**
**ANSWER**:-
All three blocks executed one after the another. I have created one sample code that helps to understand

let serialQueue = DispatchQueue(label: "SampleSerialQueue")
//Block first
serialQueue.async {
    for i in 1...10{
        print("Serial - First operation",i)
    }
}
//Block second
serialQueue.async {
    for i in 1...10{
        print("Serial - Second operation",i)
    }
}
//Block Third
serialQueue.async {
    for i in 1...10{
        print("Serial - Third operation",i)
    }
}
0
CrazyPro007