Async and Await - Wie wird die Reihenfolge der Ausführung beibehalten?

7

Ich lese gerade einige Themen über die Task Parallel Library und die asynchrone Programmierung mit async und erwarte. Das Buch "C # 5.0 in Kürze" besagt, dass der Compiler beim Warten auf einen Ausdruck mit dem Schlüsselwort await den Code in etwa so umwandelt:

%Vor%

Nehmen wir an, wir haben diese asynchrone Funktion (auch aus dem referenzierten Buch):

%Vor%

Der Aufruf der Methode 'GetPrimesCountAsync' wird in einen Pool-Thread eingereiht und ausgeführt. Im Allgemeinen hat das Aufrufen mehrerer Threads innerhalb einer for-Schleife das Potenzial für die Einführung von Race-Bedingungen.

Wie stellt die CLR sicher, dass die Anfragen in der Reihenfolge bearbeitet werden, in der sie gemacht wurden? Ich bezweifle, dass der Compiler einfach den Code in die obige Weise transformiert, da dies die 'GetPrimesCountAsync' Methode von der for-Schleife entkoppeln würde.

    
Dennis Kassel 29.07.2015, 15:54
quelle

2 Antworten

14

Nur um der Einfachheit willen werde ich Ihr Beispiel durch etwas einfacheres ersetzen, das aber alle die gleichen sinnvollen Eigenschaften hat:

%Vor%

Die Reihenfolge wird aufgrund der Definition Ihres Codes beibehalten. Stellen wir uns vor, wir gehen durch.

  1. Diese Methode wird zuerst
  2. genannt
  3. Die erste Codezeile ist die for-Schleife, also wird i initialisiert.
  4. Der Loop-Test ist erfolgreich, also gehen wir zum Körper der Schleife.
  5. SomeExpensiveComputation wird aufgerufen. Es sollte sehr schnell ein Task<T> zurückgeben, aber die Arbeit, die es tut, wird im Hintergrund weiterlaufen.
  6. Der Rest der Methode wird als Fortsetzung der zurückgegebenen Aufgabe hinzugefügt. Es wird weiter ausgeführt, wenn diese Aufgabe abgeschlossen ist.
  7. Nachdem die von SomeExpensiveComputation zurückgegebene Aufgabe abgeschlossen ist, speichern wir das Ergebnis in value .
  8. value wird auf die Konsole gedruckt.
  9. GOTO 3; Beachten Sie, dass die bestehende teure Operation bereits beendet ist, bevor wir zum zweiten Mal zu Schritt 4 kommen und den nächsten beginnen.

Soweit der C # -Compiler tatsächlich Schritt 5 ausführt, geschieht dies durch Erstellen einer Zustandsmaschine. Grundsätzlich gibt es bei jedem await eine Markierung, die anzeigt, wo es aufgehört hat, und am Anfang der Methode (oder nachdem sie nach einem Fortsetzungsfeuer wieder aufgenommen wurde) prüft sie den aktuellen Zustand und führt eine goto zum Punkt aus wo es aufgehört hat. Außerdem müssen alle lokalen Variablen in Felder einer neuen Klasse gehisst werden, damit der Status dieser lokalen Variablen beibehalten wird.

Nun wird diese Umwandlung nicht wirklich in C # -Code durchgeführt, sondern in IL, aber das ist eine Art Moraläquivalent des Codes, den ich oben in einer Zustandsmaschine gezeigt habe. Beachten Sie, dass dies nicht gültig ist C # (Sie können nicht goto in eine for -Schleife wie diese, aber diese Einschränkung gilt nicht für den IL-Code, der tatsächlich verwendet wird. Es wird auch Unterschiede zwischen diesem und was sein C # tut es tatsächlich, aber sollte Ihnen eine grundlegende Vorstellung davon geben, was hier vor sich geht:

%Vor%

Beachten Sie, dass ich für dieses Beispiel den Abbruch von Aufgaben ignoriert habe. Ich habe das gesamte Konzept des Erfassens des aktuellen Synchronisationskontextes ignoriert, es gibt ein bisschen mehr mit der Fehlerbehandlung usw. Betrachten Sie das nicht als a vollständige Implementierung.

    
Servy 29.07.2015, 16:08
quelle
7
  

Der Aufruf der Methode 'GetPrimesCountAsync' wird in die Warteschlange gestellt und in einem Pool-Thread ausgeführt.

Nein. await startet keine Hintergrundverarbeitung. Es wartet darauf, dass die vorhandene Verarbeitung abgeschlossen ist. Es ist bis zu GetPrimesCountAsync , das zu tun (z. B. mit Task.Run ). Auf diese Weise ist es klarer:

%Vor%

Die Schleife wird nur fortgesetzt, wenn die erwartete Aufgabe abgeschlossen ist. Es gibt nie mehr als eine Aufgabe.

  

Wie stellt die CLR sicher, dass die Anfragen in der Reihenfolge bearbeitet werden, in der sie gemacht wurden?

Die CLR ist nicht beteiligt.

  

Ich bezweifle, dass der Compiler den Code einfach in die obige Weise transformiert, da dies die 'GetPrimesCountAsync' Methode von der for-Schleife entkoppeln würde.

Die Transformation, die Sie anzeigen, ist grundsätzlich richtig, aber beachten Sie, dass die nächste Schleifeniteration nicht sofort gestartet wird, sondern im Callback . Dadurch wird die Ausführung serialisiert.

    
usr 29.07.2015 16:24
quelle