Wenn async-await keine zusätzlichen Threads erstellt, wie werden dann Anwendungen reaktionsfähig gemacht?

Immer wieder sehe ich, dass die Verwendung von asyncawait keine zusätzlichen Threads erzeugt. Das macht keinen Sinn, denn die einzigen Möglichkeiten, wie ein Computer mehr als eine Sache auf einmal zu tun scheint, sind

  • Mehr als eine Sache gleichzeitig erledigen (parallel ausführen, mehrere processoren nutzen)
  • Simulieren Sie es, indem Sie Aufgaben planen und zwischen ihnen wechseln (machen Sie ein bisschen A, ein bisschen B, ein bisschen A usw.)

Wenn asyncawait tut, wie kann es dann eine Anwendung ansprechen? Wenn nur ein Thread vorhanden ist, bedeutet das Aufrufen einer beliebigen Methode, dass auf die Beendigung der Methode gewartet wird, bevor irgendetwas anderes ausgeführt wird, und die Methoden innerhalb dieser Methode auf das Ergebnis warten müssen, bevor sie fortfahren können und so weiter.

Async / erwarten ist nicht wirklich magisch. Das vollständige Thema ist ziemlich weit gefasst, aber für eine schnelle, aber vollständige Antwort auf Ihre Frage, die ich denke, können wir es schaffen.

Lassen Sie uns ein einfaches Klickereignis in einer Windows Forms-Anwendung angehen:

 public async void button1_Click(object sender, EventArgs e) { Console.WriteLine("before awaiting"); await GetSomethingAsync(); Console.WriteLine("after awaiting"); } 

Ich werde nicht explizit über das sprechen, was es ist. GetSomethingAsync kehrt jetzt zurück. Sagen wir einfach, das ist etwas, das nach etwa 2 Sekunden abgeschlossen wird.

In einer herkömmlichen, nicht-asynchronen Welt würde Ihr Ereignishandler für das Klicken auf Schaltflächen etwa so aussehen:

 public void button1_Click(object sender, EventArgs e) { Console.WriteLine("before waiting"); DoSomethingThatTakes2Seconds(); Console.WriteLine("after waiting"); } 

Wenn Sie auf die Schaltfläche in dem Formular klicken, wird die Anwendung für ca. 2 Sekunden einfrieren angezeigt, während wir warten, bis diese Methode abgeschlossen ist. Was passiert, ist, dass die “Nachrichtenpumpe”, im Grunde eine Schleife, blockiert ist.

Diese Schleife fragt ständig Fenster “Hat jemand etwas getan, wie die Maus bewegt, etwas angeklickt? Muss ich etwas neu streichen? Wenn ja, sag es mir!” und dann verarbeitet das “etwas”. Diese Schleife hat eine Nachricht erhalten, dass der Benutzer auf “button1” geklickt hat (oder die entsprechende Art von Nachricht von Windows), und endete damit, unsere button1_Click Methode oben button1_Click . Bis diese Methode zurückkehrt, bleibt diese Schleife hängen. Dies dauert 2 Sekunden und währenddessen werden keine Nachrichten verarbeitet.

Die meisten Dinge, die mit Windows zu tun haben, werden mit Hilfe von Nachrichten erledigt, was bedeutet, dass wenn der Nachrichten-Loop aufhört, Nachrichten zu pumpen, auch nur für eine Sekunde, für den Benutzer schnell bemerkbar ist. Wenn Sie zum Beispiel Notepad oder ein anderes Programm über Ihr eigenes Programm bewegen und dann wieder weggehen, wird eine Flut von Paint-Nachrichten an Ihr Programm gesendet, die angeben, welcher Bereich des Fensters plötzlich wieder sichtbar wurde. Wenn die Nachrichtenschleife, die diese Nachrichten verarbeitet, auf etwas wartet, blockiert, wird kein Bild erstellt.

Also, wenn im ersten Beispiel async/await keine neuen Threads erstellt, wie macht es das?

Nun, was passiert, ist, dass Ihre Methode in zwei Teile geteilt wird. Dies ist einer dieser allgemeinen Themen, also werde ich nicht zu sehr ins Detail gehen, aber es genügt zu sagen, dass die Methode in diese zwei Dinge aufgeteilt ist:

  1. Der gesamte Code, der dazu führt, dass er await , einschließlich des Aufrufs von GetSomethingAsync
  2. Alle folgenden Codes await

Illustration:

 code... code... code... await X(); ... code... code... code... 

Umsortiert:

 code... code... code... var x = X(); await X; code... code... code... ^ ^ ^ ^ +---- portion 1 -------------------+ +---- portion 2 ------+ 

Grundsätzlich läuft die Methode so ab:

  1. Es führt alles aus, await zu await
  2. Es ruft die GetSomethingAsync Methode auf, die ihre GetSomethingAsync erledigt, und gibt etwas zurück, das in der Zukunft 2 Sekunden dauern wird

    Bisher befinden wir uns noch im ursprünglichen Aufruf von button1_Click, der im Hauptthread stattfindet und von der Nachrichtenschleife aus aufgerufen wird. Wenn der Code, der zum await führt, viel Zeit in await nimmt, wird die Benutzeroberfläche immer noch eingefroren. In unserem Beispiel nicht so sehr

  3. Was das ” await -Schlüsselwort zusammen mit etwas cleverer Compiler-Magie ist, ist, dass es im Grunde so etwas wie “Ok, weißt du was, ich komme einfach vom Button Click Event Handler hier zurück. Wenn du (wie in dem Ding wir warten darauf), um zu vervollständigen, lassen Sie es mich wissen, weil ich noch etwas Code zur Ausführung habe “.

    Tatsächlich wird es der SynchronizationContext-class mitteilen, dass dies erledigt ist, was abhängig vom tatsächlichen Synchronisationskontext, der gerade im Spiel ist, in die Warteschlange gestellt wird. Die Kontextklasse, die in einem Windows Forms-Programm verwendet wird, wird in die Warteschlange gestellt, die die Warteschlange verwendet, die die Nachrichtenschleife pumpt.

  4. So kehrt es zur Nachrichtenschleife zurück, die nun frei ist, Nachrichten weiter zu pumpen, wie das Verschieben des Fensters, das Ändern der Größe oder das Klicken auf andere Schaltflächen.

    Für den Benutzer reactjs die Benutzeroberfläche jetzt wieder und verarbeitet andere Schaltflächenklicks, die Größenänderung und vor allem das Neuzeichnen , sodass sie nicht zu blockieren scheint.

  5. 2 Sekunden später ist die Sache, auf die wir warten, abgeschlossen, und was jetzt passiert, ist, dass es (nun, der Synchronisationskontext) eine Nachricht in die Warteschlange der Nachrichtenschleife stellt und sagt: “Hey, ich habe mehr Code für Sie ausführen “, und dieser Code ist der gesamte Code nach dem warten.
  6. Wenn die Nachrichtenschleife zu dieser Nachricht gelangt, wird sie im Grunde diese Methode, an der sie unterbrochen wurde, erneut “betreten”, unmittelbar nachdem sie await und den Rest der Methode weiter ausführt. Beachten Sie, dass dieser Code erneut aus der Nachrichtenschleife aufgerufen wird. Wenn dieser Code etwas async/await tut, ohne async/await ordnungsgemäß zu verwenden, wird die Nachrichtenschleife erneut blockiert

Es gibt viele bewegliche Teile hier unter der Haube, also hier sind einige Links zu mehr Informationen, ich würde sagen “sollten Sie es brauchen”, aber dieses Thema ist ziemlich breit und es ist ziemlich wichtig, einige dieser beweglichen Teile zu wissen. Unweigerlich wirst du verstehen, dass async / await immer noch ein undichtes Konzept ist. Einige der zugrunde liegenden Einschränkungen und Probleme gehen immer noch in den umgebenden Code über, und wenn sie dies nicht tun, müssen Sie normalerweise eine Anwendung debuggen, die zufällig aus scheinbar keinem guten Grund bricht.

  • Asynchrones Programmieren mit Async und Warten (C # und Visual Basic)
  • SynchronizationContext-class
  • Stephen Cleary – Es gibt keinen wirklich lesenswerten Thread !
  • Channel 9 – Mads Torgersen: Inside C # Async ist eine Uhr wert!

OK, was ist, wenn GetSomethingAsync einen Thread GetSomethingAsync , der in 2 Sekunden fertig ist? Ja, dann ist offensichtlich ein neuer Thread im Spiel. Dieser Thread ist jedoch nicht wegen der Async-ness dieser Methode, weil der Programmierer dieser Methode einen Thread zum Implementieren von asynchronem Code ausgewählt hat. Fast alle asynchronen E / A verwenden keinen Thread, sie verwenden verschiedene Dinge. async/await selbst keine neuen Threads, aber offensichtlich können die “Dinge, auf die wir warten” mithilfe von Threads implementiert werden.

Es gibt viele Dinge in .NET, die nicht notwendigerweise einen Thread selbst erzeugen, aber immer noch asynchron sind:

  • Web-Anfragen (und viele andere netzwerkbezogene Dinge, die Zeit brauchen)
  • Asynchrones Lesen und Schreiben von Dateien
  • und viele mehr, ein gutes Zeichen ist, wenn die betreffende class / Schnittstelle Methoden namens SomethingSomethingAsync oder BeginSomething und EndSomething und ein IAsyncResult beteiligt ist.

Normalerweise verwenden diese Dinge keinen Faden unter der Haube.


OK, also wollen Sie etwas von diesem “breiten Themen-Zeug”?

Nun, lassen Sie uns fragen Try Roslyn über unseren Knopf klicken:

Versuchen Sie Roslyn

Ich werde hier nicht in der vollständig generierten class verlinken, aber es ist ziemlich blödes Zeug.

Die einzigen Möglichkeiten, wie ein Computer mehr als 1 Sache auf einmal zu tun scheint, ist (1) Tatsächlich mehr als 1 Sache zu einer Zeit zu machen, (2) ihn zu simulieren, indem Aufgaben geplant und zwischen ihnen umgeschaltet wird. Also wenn async-await das nicht tut

Es ist nicht so, dass keiner von denen wartet. Erinnern Sie sich, der Zweck des await besteht nicht darin , synchronen Code magisch asynchron zu machen . Es ermöglicht die Verwendung der gleichen Techniken, die wir zum Schreiben von synchronem Code beim Aufruf von asynchronem Code verwenden . Es wird erwartet, dass der Code mit Operationen mit hoher Latenz wie Code aussieht, der Operationen mit niedriger Latenz verwendet . Diese Operationen mit hoher Latenz können sich in Threads befinden, sie befinden sich möglicherweise auf Spezialhardware, sie können ihre Arbeit in kleine Stücke zerlegen und sie in die Nachrichtenwarteschlange zur späteren Verarbeitung durch den UI-Thread einfügen. Sie tun etwas , um Asynchronität zu erreichen, aber sie sind es, die es tun. Warten Sie nur lässt Sie diese Asynchronität ausnutzen.

Außerdem denke ich, dass Sie eine dritte Option vermissen. Wir alten Leute – Kinder heute mit ihrer Rap-Musik sollten von meinem Rasen verschwinden usw. – erinnern Sie sich an die Welt von Windows in den frühen 1990ern. Es gab keine Multi-CPU-Maschinen und keine Thread-Scheduler. Sie wollten zwei Windows-Apps gleichzeitig ausführen, Sie mussten nachgeben . Multitasking war kooperativ . Das Betriebssystem teilt einem process mit, dass es ausgeführt werden soll, und wenn es sich schlecht verhält, verhungert es, dass alle anderen processe bedient werden. Es läuft so lange, bis es nachgibt, und irgendwie muss es wissen, wo es das nächste Mal aufhört, wenn das Betriebssystem die Steuerung zurückführt . Single-Threaded asynchroner Code ist sehr ähnlich mit “erwarten” statt “Ausbeute”. Erwarten heißt: “Ich werde mich daran erinnern, wo ich hier aufgehört habe, und jemand anderen für eine Weile laufen lassen; rufen Sie mich zurück, wenn die Aufgabe, auf die ich warte, abgeschlossen ist, und ich werde dort weitermachen, wo ich aufgehört habe.” Ich denke, Sie können sehen, wie Apps so reaktionsschnell werden, wie in den Windows 3-Tagen.

Wenn Sie eine Methode aufrufen, müssen Sie warten, bis die Methode abgeschlossen ist

Da ist der Schlüssel, den du vermisst. Eine Methode kann zurückkehren, bevor ihre Arbeit abgeschlossen ist . Das ist die Essenz der Asynchronität genau dort. Eine Methode gibt zurück, sie gibt eine Aufgabe zurück, die bedeutet “diese Arbeit wird gerade ausgeführt; sagen Sie mir, was zu tun ist, wenn sie abgeschlossen ist”. Die Arbeit der Methode ist nicht getan, obwohl sie zurückgekehrt ist .

Vor dem Warte-Operator mussten Sie Code schreiben, der aussah wie Spaghetti, die durch Schweizer Käse gefädelt sind, um mit der Tatsache umzugehen, dass wir nach der Fertigstellung Arbeit haben , aber mit der Rückkehr und der Desynchronisierung . Erwartet, dass Sie Code schreiben können, der aussieht, als ob die Rückgabe und die Beendigung synchronisiert werden, ohne dass sie tatsächlich synchronisiert werden.

Ich erkläre es in meinem Blog Post There Is No Thread vollständig.

Zusammenfassend wird bei modernen I / O-Systemen DMA (Direct Memory Access) stark genutzt. Es gibt spezielle, dedizierte processoren auf Netzwerkkarten, Grafikkarten, HDD-Controllern, seriellen / parallelen Anschlüssen usw. Diese processoren haben direkten Zugriff auf den Speicherbus und handhaben das Lesen / Schreiben vollständig unabhängig von der CPU. Die CPU muss lediglich das Gerät über den Ort im Speicher informieren, der die Daten enthält, und kann dann ihr eigenes Ding ausführen, bis das Gerät einen Interrupt austriggers, der der CPU mitteilt, dass das Lesen / Schreiben abgeschlossen ist.

Sobald die Operation im Flug ist, gibt es keine Arbeit für die CPU und somit keinen Thread.

Ich bin wirklich froh, dass jemand diese Frage gestellt hat, denn ich glaubte auch lange, dass Threads für die Parallelität notwendig sind. Als ich zum ersten Mal Ereignisschleifen sah, dachte ich, sie wären eine Lüge. Ich dachte mir: “Es gibt keine Möglichkeit, dass dieser Code gleichzeitig ausgeführt werden kann, wenn er in einem einzigen Thread ausgeführt wird”. Denken Sie daran, dies ist, nachdem ich bereits den Kampf des Verständnisses des Unterschieds zwischen Nebenläufigkeit und Parallelismus durchlaufen hatte.

Nach eigenen Recherchen fand ich endlich das fehlende Stück: select() . Genauer gesagt, E / A-Multiplexing, implementiert von verschiedenen coreen unter verschiedenen Namen: select() , poll() , epoll() , kqueue() . Dies sind Systemaufrufe , die, während die Implementierungsdetails unterschiedlich sind, Ihnen ermöglichen, eine Gruppe von Dateideskriptoren zur Überwachung zu übergeben. Dann können Sie einen weiteren Anruf tätigen, der blockiert, bis sich einer der überwachten Dateideskriptoren ändert.

Daher kann man auf eine Menge von IO-Ereignissen (die Hauptereignisschleife) warten, das erste abgeschlossene Ereignis behandeln und dann die Steuerung zurück zur Ereignisschleife bringen. Spülen und wiederholen.

Wie funktioniert das? Nun, die kurze Antwort ist, dass es Magie auf coreel und Hardwareebene ist. Es gibt viele Komponenten in einem Computer neben der CPU, und diese Komponenten können parallel arbeiten. Der coreel kann diese Geräte steuern und direkt mit ihnen kommunizieren, um bestimmte Signale zu empfangen.

Diese E / A-Multiplex-Systemaufrufe sind der grundlegende Baustein von Singlethread-Ereignisschleifen wie node.js oder Tornado. Wenn Sie await eine function await , achten Sie auf ein bestimmtes Ereignis (die Fertigstellung dieser function) und geben dann die Steuerung an die Hauptereignisschleife zurück. Wenn das Ereignis, das Sie ansehen, beendet ist, wird die function (eventuell) von dort wieder aufgenommen, wo sie aufgehört hat. functionen, mit denen Sie Berechnungen wie diese anhalten und fortsetzen können, werden als Koroutinen bezeichnet .

await und async verwenden Aufgaben nicht Threads.

Das Framework hat einen Pool von Threads, die bereit sind, etwas Arbeit in der Form von Task- Objekten auszuführen; Senden einer Aufgabe an den Pool bedeutet, dass Sie einen freien, bereits vorhandenen 1- Thread auswählen, um die Task-Aktionsmethode aufzurufen.
Beim Erstellen einer Aufgabe muss ein neues Objekt sehr viel schneller erstellt werden als mit einem neuen Thread.

Wenn eine Aufgabe hinzugefügt werden kann, um eine Continuation anzuhängen, handelt es sich um ein neues Task- Objekt, das nach Beendigung des Threads ausgeführt wird.

Da async/await Task s async/await , erstellen sie keinen neuen Thread.


Obwohl Interrupt-Programmiertechnik in jedem modernen Betriebssystem weit verbreitet ist, halte ich sie hier nicht für relevant.
Sie können zwei CPU-gebundene Tasks ausführen, die parallel (tatsächlich verschachtelt) in einer einzelnen CPU mit aysnc/await .
Das kann nicht einfach mit der Tatsache erklärt werden, dass das OS die Warteschlangen- IORP unterstützt .


Beim letzten Mal, als ich die async den des Compilers in DFA überprüfte, ist die Arbeit in Schritte unterteilt, von denen jeder mit einem await endet.
Die await startet ihre Aufgabe und fügt sie eine Fortsetzung ein, um den nächsten Schritt auszuführen.

Als ein Konzeptbeispiel ist hier ein Pseudocode-Beispiel.
Die Dinge werden der Übersichtlichkeit halber vereinfacht und weil ich mich nicht genau an alle Details erinnere.

 method: instr1 instr2 await task1 instr3 instr4 await task2 instr5 return value 

Es wird in so etwas verwandelt

 int state = 0; Task nextStep() { switch (state) { case 0: instr1; instr2; state = 1; task1.addContinuation(nextStep()); task1.start(); return task1; case 1: instr3; instr4; state = 2; task2.addContinuation(nextStep()); task2.start(); return task2; case 2: instr5; state = 0; task3 = new Task(); task3.setResult(value); task3.setCompleted(); return task3; } } method: nextStep(); 

1 Tatsächlich kann ein Pool seine Aufgabenerstellungsrichtlinie haben.

Ich werde nicht mit Eric Lippert oder Lasse V. Karlsen konkurrieren, und andere möchte ich nur auf eine weitere Facette dieser Frage aufmerksam machen, die meiner Meinung nach nicht explizit erwähnt wurde.

Wenn Sie auf sich selbst await , wird Ihre App nicht magisch reagieren. Was auch immer Sie in der Methode tun, auf die Sie von den UI-Thread-Blöcken warten, es wird Ihre UI immer noch auf dieselbe Weise blockieren wie eine nicht erwartete Version .

Sie müssen Ihre gewünschte Methode speziell schreiben, so dass entweder ein neuer Thread erzeugt wird oder ein Completion-Port verwendet wird (der die Ausführung im aktuellen Thread zurückgibt und etwas anderes zur Fortsetzung aufruft, sobald der Completion-Port signalisiert wird). Aber dieser Teil wird in anderen Antworten gut erklärt.

Hier ist, wie ich all das sehe, es ist vielleicht nicht super technisch genau, aber es hilft mir, zumindest :).

Es gibt grundsätzlich zwei Arten der Verarbeitung (Berechnung) auf einer Maschine:

  • Verarbeitung, die auf der CPU geschieht
  • Verarbeitung, die auf anderen processoren (GPU, Netzwerkkarte, etc.) geschieht, nennen wir sie IO.

Wenn wir also nach der Kompilierung einen Teil des Quellcodes schreiben, abhängig von dem Objekt, das wir verwenden (und das ist sehr wichtig), wird die Verarbeitung CPU-gebunden oder IO-gebunden sein , und tatsächlich kann sie an eine Kombination von beide.

Einige Beispiele:

  • Wenn ich die Write-Methode des FileStream Objekts verwende (was ein Stream ist), wird die Verarbeitung sagen, 1% CPU gebunden und 99% IO gebunden.
  • Wenn ich die Write-Methode des NetworkStream Objekts (das ein Stream ist) verwenden, wird die Verarbeitung sagen, 1% CPU gebunden und 99% IO gebunden.
  • Wenn ich die Write-Methode des Memorystream Objekts verwende (was ein Stream ist), wird die Verarbeitung zu 100% CPU-gebunden sein.

Wie Sie sehen, hängt es vom Standpunkt eines objektorientierten Programmierers aus, obwohl ich immer auf ein Stream Objekt zugreife, dass das, was darunter passiert, stark vom ultimativen Typ des Objekts abhängen kann.

Nun, um Dinge zu optimieren, ist es manchmal nützlich, Code parallel laufen zu lassen (beachte, dass ich das Wort asynchron nicht verwende), wenn es möglich und / oder notwendig ist.

Einige Beispiele:

  • In einer Desktop-App möchte ich ein Dokument drucken, aber ich möchte nicht darauf warten.
  • Mein Webserver Server viele Clients zur gleichen Zeit, jeder erhält seine Seiten parallel (nicht serialisiert).

Vor async / have hatten wir im Wesentlichen zwei Lösungen:

  • Themen . Es war relativ einfach zu verwenden, mit Thread- und ThreadPool-classn. Threads sind nur CPU-gebunden .
  • Das “alte” asynchrone Start / End / AsyncCallback- Programmiermodell. Es ist nur ein Modell, es sagt dir nicht, ob du CPU oder IO gebunden bist. Wenn Sie sich die Socket- oder FileStream-classn ansehen, ist es IO-gebunden, was cool ist, aber wir verwenden es nur selten.

Async / await ist nur ein gemeinsames Programmiermodell, das auf dem Task-Konzept basiert . Es ist ein wenig einfacher zu verwenden als Threads oder Thread-Pools für CPU-gebundene Aufgaben und viel einfacher zu verwenden als das alte Begin / End-Modell. Undercover ist jedoch “nur” ein super ausgefeilter Feature-Full Wrapper für beide.

Also, der wirkliche Gewinn ist meistens auf IO Bound Tasks , Task, die nicht die CPU benutzen, aber async / wait ist immernoch nur ein Programmiermodell, es hilft dir nicht zu bestimmen, wie / wo die Verarbeitung am Ende passieren wird.

Es bedeutet nicht, dass eine class eine Methode “DoSomethingAsync” hat, die ein Task-Objekt zurückgibt, von dem angenommen wird, dass es CPU-gebunden ist (was bedeutet, dass es ziemlich nutzlos ist , insbesondere wenn es keinen Abbruch-Token-Parameter hat) oder IO-gebunden (was bedeutet, dass es wahrscheinlich ein Muss ist ), oder eine Kombination von beiden (da das Modell ziemlich viral ist, können Bindung und potentielle Vorteile am Ende super gemischt und nicht so offensichtlich sein).

Wenn ich also zu meinen Beispielen zurückkehre und meine Write-Operationen mit async / await auf MemoryStream ausführen, bleibe ich CPU-gebunden (ich werde wahrscheinlich nicht davon profitieren), obwohl ich sicherlich davon mit Dateien und Netzwerkstreams profitieren werde.

Zusammenfassung anderer Antworten:

Async / await wird in erster Linie für E / A-gebundene Tasks erstellt, da sie die Verwendung des blockierenden Threads verhindern können.

Im Falle von E / A-gebundenen Aufgaben besteht der primäre Vorteil darin, das Blockieren des UI-Threads zu vermeiden. Für Nicht-UI-Threads könnte man performancesvorteile haben.

Async erstellt keinen eigenen Thread. Der Thread der aufrufenden Methode wird verwendet, um die asynchrone Methode auszuführen, bis sie ein erwartetes findet. Derselbe Thread führt dann den Rest der aufrufenden Methode über den asynchronen Methodenaufruf hinaus aus. Innerhalb der aufgerufenen asynchronen Methode kann die Fortsetzung nach dem Zurückkehren von dem Erwarteten auf einem Thread aus dem Thread-Pool ausgeführt werden – der einzige Ort, an dem ein separater Thread ins Bild kommt.

Tatsächlich sind async await durch den CLR-Compiler erzeugte Zustandsmaschine.

async await jedoch auf Threads, die TPL Thread-Pool verwenden, um Aufgaben auszuführen.

Der Grund, warum die Anwendung nicht blockiert ist, besteht darin, dass die Zustandsmaschine entscheiden kann, welche Co-Routine auszuführen, zu wiederholen, zu prüfen und erneut zu entscheiden ist.

Weiterführende Literatur:

Was erzeugt async & await?

Async-Wartezeit und die generierte StateMachine

Asynchrone C # und F # (III.): Wie funktioniert es? – Tomas Petricek

Bearbeiten :

Okay. Es scheint, dass meine Ausarbeitung falsch ist. Allerdings muss ich darauf hinweisen, dass State-Maschinen wichtige async await Assets sind. Selbst wenn Sie asynchrone I / O-Operationen ausführen, benötigen Sie noch einen Helper, um zu überprüfen, ob die Operation abgeschlossen ist. Daher benötigen wir noch eine Zustandsmaschine und bestimmen, welche Routine asynchron zusammen ausgeführt werden kann.