Eigentlich ist Async / Warten nicht so magisch. Das vollständige Thema ist ziemlich weit gefasst, aber für eine schnelle und dennoch vollständige Antwort auf Ihre Frage denke ich, dass wir es schaffen können.
Lassen Sie uns ein einfaches Ereignis zum Klicken auf eine Schaltfläche 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 explizit nicht darüber sprechen, was es GetSomethingAsync
gerade zurückgibt. Sagen wir einfach, dies ist etwas, das nach beispielsweise 2 Sekunden abgeschlossen sein wird.
In einer traditionellen, nicht asynchronen Welt würde Ihr Ereignishandler für Schaltflächenklicks ungefähr 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 im Formular klicken, scheint die Anwendung etwa 2 Sekunden lang einzufrieren, während wir auf den Abschluss dieser Methode warten. Was passiert ist, dass die "Nachrichtenpumpe", im Grunde eine Schleife, blockiert ist.
Diese Schleife fragt Windows ständig: "Hat jemand etwas getan, wie die Maus bewegt, auf etwas geklickt? Muss ich etwas neu streichen? Wenn ja, sagen Sie es mir!" und verarbeitet dann das "etwas". Diese Schleife erhielt eine Nachricht, dass der Benutzer auf "button1" (oder den entsprechenden Nachrichtentyp von Windows) geklickt hat, und rief schließlich unsere anbutton1_Click
oben beschriebene Methode aufgerufen hat. Bis diese Methode zurückkehrt, wartet diese Schleife nicht mehr. Dies dauert 2 Sekunden und währenddessen werden keine Nachrichten verarbeitet.
Die meisten Dinge, die sich mit Fenstern befassen, werden mithilfe von Nachrichten ausgeführt. Wenn die Nachrichtenschleife auch nur für eine Sekunde keine Nachrichten mehr pumpt, fällt dies dem Benutzer schnell auf. Wenn Sie beispielsweise den Notizblock oder ein anderes Programm über Ihr eigenes Programm bewegen und dann wieder entfernen, wird eine Reihe von Malmeldungen an Ihr Programm gesendet, die angeben, welcher Bereich des Fensters jetzt plötzlich wieder sichtbar wurde. Wenn die Nachrichtenschleife, die diese Nachrichten verarbeitet, auf etwas wartet, das blockiert ist, wird kein Malvorgang ausgeführt.
Wenn im ersten Beispiel async/await
keine neuen Threads erstellt werden, wie funktioniert das?
Nun, was passiert ist, dass Ihre Methode in zwei Teile geteilt ist. Dies ist eine dieser allgemeinen Themen, daher werde ich nicht zu sehr ins Detail gehen, aber es reicht zu sagen, dass die Methode in diese beiden Dinge unterteilt ist:
- Der gesamte Code, der dazu führt
await
, einschließlich des Aufrufs vonGetSomethingAsync
- Der gesamte Code folgt
await
Illustration:
code... code... code... await X(); ... code... code... code...
Neu arrangiert:
code... code... code... var x = X(); await X; code... code... code...
^ ^ ^ ^
+---- portion 1 -------------------+ +---- portion 2 ------+
Grundsätzlich läuft die Methode folgendermaßen ab:
- Es führt alles bis zu
await
Es ruft die GetSomethingAsync
Methode auf, die ihre Sache macht, und gibt etwas zurück, das in 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 aufgerufen wird. Wenn der Code im Vorfeld await
viel Zeit in Anspruch nimmt, friert die Benutzeroberfläche immer noch ein. In unserem Beispiel nicht so sehr
Was das await
Schlüsselwort zusammen mit etwas cleverer Compiler-Magie bewirkt, ist, dass es im Grunde so etwas wie "Ok, weißt du was ? Ich werde einfach vom Button-Click-Event-Handler hier zurückkehren. Wenn du (wie in, das, was wir ' Warten Sie auf), um fertig zu werden. Lassen Sie es mich wissen, da ich noch Code zum Ausführen habe. "
Tatsächlich wird die SynchronizationContext-Klasse darüber informiert, dass dies abgeschlossen ist. Abhängig vom aktuellen Synchronisierungskontext, der gerade im Spiel ist, wird die Warteschlange für die Ausführung anstehen. Die in einem Windows Forms-Programm verwendete Kontextklasse stellt sie mithilfe der Warteschlange in die Warteschlange, die die Nachrichtenschleife pumpt.
Es kehrt also zur Nachrichtenschleife zurück, die nun frei ist, Nachrichten weiter zu pumpen, z. B. das Fenster zu verschieben, die Größe zu ändern oder auf andere Schaltflächen zu klicken.
Für den Benutzer reagiert die Benutzeroberfläche jetzt wieder, verarbeitet andere Schaltflächenklicks, ändert die Größe und vor allem zeichnet sie neu , sodass sie nicht einzufrieren scheint.
- 2 Sekunden später ist das, worauf wir warten, abgeschlossen und was jetzt passiert, ist, dass es (nun, der Synchronisationskontext) eine Nachricht in die Warteschlange stellt, die die Nachrichtenschleife betrachtet, und sagt: "Hey, ich habe noch etwas Code für Sie ausführen ", und dieser Code ist der gesamte Code nach dem Warten.
- Wenn die Nachrichtenschleife zu dieser Nachricht gelangt, wird sie die Methode, an der sie aufgehört hat, unmittelbar danach "erneut eingeben"
await
und den Rest der Methode weiter ausführen. Beachten Sie, dass dieser Code erneut aus der Nachrichtenschleife aufgerufen wird. Wenn dieser Code also etwas Langes tut, ohne ihn async/await
ordnungsgemäß zu verwenden, blockiert er die Nachrichtenschleife erneut
Es gibt hier viele bewegliche Teile unter der Haube, daher hier einige Links zu weiteren Informationen. Ich wollte sagen, "falls Sie es brauchen", aber dieses Thema ist ziemlich weit gefasst und es ist ziemlich wichtig, einige dieser beweglichen Teile zu kennen . Sie werden immer verstehen, dass Async / Warten immer noch ein undichtes Konzept ist. Einige der zugrunde liegenden Einschränkungen und Probleme fließen immer noch in den umgebenden Code ein. Wenn dies nicht der Fall ist, müssen Sie normalerweise eine Anwendung debuggen, die ohne ersichtlichen Grund zufällig unterbrochen wird.
OK, was ist, wenn GetSomethingAsync
ein Thread hochgefahren wird, der in 2 Sekunden abgeschlossen ist? Ja, dann ist offensichtlich ein neuer Thread im Spiel. Dieser Thread ist jedoch nicht , weil die Async-heit dieses Verfahrens ist es, weil der Programmierer dieses Verfahrens ein Gewinde wählt asynchronen Code zu implementieren. Fast alle asynchronen E / A verwenden keinen Thread, sondern unterschiedliche Dinge. async/await
selbst drehen keine neuen Threads hoch, aber offensichtlich können die "Dinge, auf die wir warten" mithilfe von Threads implementiert werden.
Es gibt viele Dinge in .NET, die nicht unbedingt einen eigenen Thread starten, aber dennoch asynchron sind:
- Webanfragen (und viele andere netzwerkbezogene Dinge, die Zeit brauchen)
- Asynchrones Lesen und Schreiben von Dateien
- und vieles mehr ist ein gutes Zeichen, wenn die betreffende Klasse / Schnittstelle Methoden mit dem Namen
SomethingSomethingAsync
oder BeginSomething
und hat EndSomething
und eine IAsyncResult
beteiligt ist.
Normalerweise verwenden diese Dinge keinen Faden unter der Haube.
OK, möchten Sie etwas von diesem "breiten Thema"?
Fragen wir Try Roslyn nach unserem Button-Klick:
Versuchen Sie es mit Roslyn
Ich werde hier nicht in der vollständig generierten Klasse verlinken, aber es ist ziemlich blutiges Zeug.