Entspricht das Schlüsselwort Async await einem ContinueWith-Lambda?

79

Könnte jemand bitte so freundlich sein, zu bestätigen, dass ich das Schlüsselwort Async await richtig verstanden habe? (Mit Version 3 des CTP)

Bisher habe ich herausgefunden, dass das Einfügen des Schlüsselworts await vor einem Methodenaufruf im Wesentlichen zwei Dinge bewirkt: A. Es erzeugt eine sofortige Rückgabe und B. Es erzeugt eine "Fortsetzung", die nach Abschluss des asynchronen Methodenaufrufs aufgerufen wird. In jedem Fall ist die Fortsetzung der Rest des Codeblocks für die Methode.

Ich frage mich also, ob diese beiden Codebits technisch äquivalent sind. Wenn ja, bedeutet dies im Grunde, dass das Schlüsselwort await mit dem Erstellen eines ContinueWith Lambda identisch ist (dh, es ist im Grunde eine Compiler-Verknüpfung für eines). Wenn nicht, was sind die Unterschiede?

bool Success =
    await new POP3Connector(
        "mail.server.com", txtUsername.Text, txtPassword.Text).Connect();
// At this point the method will return and following code will
// only be invoked when the operation is complete(?)
MessageBox.Show(Success ? "Logged In" : "Wrong password");

VS

(new POP3Connector(
    "mail.server.com", txtUsername.Text, txtPassword.Text ).Connect())
.ContinueWith((success) =>
    MessageBox.Show(success.Result ? "Logged In" : "Wrong password"));
Maxim Gershkovich
quelle

Antworten:

82

Die allgemeine Idee ist richtig - der Rest der Methode wird zu einer Art Fortsetzung gemacht.

Der Blog-Beitrag "Fast Path" enthält Details zur Funktionsweise der async/ awaitcompiler-Transformation.

Unterschiede auf den ersten Blick:

Das awaitSchlüsselwort verwendet auch ein "Planungskontext" -Konzept. Der Planungskontext ist, SynchronizationContext.Currentfalls vorhanden, ein Rückgriff auf TaskScheduler.Current. Die Fortsetzung wird dann im Planungskontext ausgeführt. So eine engere Annäherung wäre passieren TaskScheduler.FromCurrentSynchronizationContextin ContinueWith, fiel auf der Rückseite , TaskScheduler.Currentwenn nötig.

Die tatsächliche async/ awaitImplementierung basiert auf dem Mustervergleich; Es wird ein "wartbares" Muster verwendet, mit dem neben Aufgaben auch andere Dinge erwartet werden können. Einige Beispiele sind die asynchronen WinRT-APIs, einige spezielle Methoden wie YieldRx-Observables und spezielle Socket-Waitables, die den GC nicht so hart treffen . Aufgaben sind mächtig, aber sie sind nicht die einzigen, die erwartet werden.

Ein weiterer kleiner Unterschied fällt ein: Wenn das Warten bereits abgeschlossen ist, asynckehrt die Methode zu diesem Zeitpunkt nicht zurück. es geht synchron weiter. Es ist also wie ein Pass TaskContinuationOptions.ExecuteSynchronously, aber ohne die stapelbezogenen Probleme.

Stephen Cleary
quelle
2
Sehr gut gesagt - ich versuche, mich auf Jons Posts zu beschränken, da sie so viel umfangreicher sind als alles, was ich Zeit hätte, um eine Antwort auf SO zu geben, aber Stephen hat absolut Recht. WRT was erwartet (und GetAwaiter insbesondere), sein Beitrag # 3 ist meiner Meinung nach
James Manning
4
Stephens Platz hier. Für einfache Beispiele ist es leicht zu glauben, dass Async / Warten nur eine Abkürzung für ContinueWith ist - ich denke jedoch gerne umgekehrt darüber nach. Async / await ist ein leistungsfähigerer Ausdruck dessen, wofür Sie ContinueWith verwendet haben. Das Problem ist, dass ContinueWith (...) Lambdas verwendet und die Übertragung auf die Fortsetzung ermöglicht. Andere Kontrollflusskonzepte wie Schleifen sind jedoch ziemlich unmöglich, wenn Sie die Hälfte des Schleifenkörpers vor ContinueWith (..) stellen müssen. .) und die andere Hälfte danach. Sie erhalten eine manuelle Fortsetzung der Verkettung.
Theo Yaung
7
Ein weiteres Beispiel, bei dem async / await viel aussagekräftiger ist als ContinueWith (...), sind Ausnahmen. Sie können innerhalb desselben try-Blocks mehrmals warten, und für jede Ausführungsphase können ihre Ausnahmen an denselben catch-Block (...) weitergeleitet werden, ohne dass Tonnen von Code geschrieben werden müssen, die dies explizit tun.
Theo Yaung
2
Der letzte Teil von async / await, der bemerkenswert ist, ist, dass es sich um ein "übergeordnetes Konzept" handelt, während ContinueWith (...) manueller ist und explizit Lambdas, delegierte Kreationen usw. enthält. Bei übergeordneten Konzepten gibt es mehr Möglichkeiten zur Optimierung - also für Beispiel: Mehrere Wartezeiten in derselben Methode "teilen" tatsächlich denselben Lambda-Abschluss (dies entspricht dem Overhead eines einzelnen Lambda), während ContinueWith (...) den Overhead jedes Mal erhält, wenn Sie es aufrufen, da Sie explizit ein Lambda geschrieben haben. Der Compiler gibt Ihnen das also.
Theo Yaung
1
@MobyDisk: Zur Verdeutlichung werden awaitimmer noch SynchronizationContext.Currentso aufgenommen, wie es immer war. Aber auf ASP.NET Core SynchronizationContext.Currentist null.
Stephen Cleary
8

Es ist "im Wesentlichen" das, aber der generierte Code macht genau mehr als nur das. Für viel mehr Details zum generierten Code würde ich Jon Skeets Eduasync-Serie wärmstens empfehlen:

http://codeblog.jonskeet.uk/category/eduasync/

Insbesondere geht Post # 7 auf das ein, was generiert wird (ab CTP 2) und warum, also wahrscheinlich eine gute Ergänzung für das, wonach Sie gerade suchen:

http://codeblog.jonskeet.uk/2011/05/20/eduasync-part-7-generated-code-from-a-simple-async-method/

EDIT: Ich denke, es ist wahrscheinlich detaillierter als das, wonach Sie in der Frage suchen, aber wenn Sie sich fragen, wie Dinge aussehen, wenn Sie mehrere Wartezeiten in der Methode haben, wird dies in Beitrag 9 behandelt :)

http://codeblog.jonskeet.uk/2011/05/30/eduasync-part-9-generated-code-for-multiple-awaits/

James Manning
quelle