Verwischen der Linien zwischen asynchronen und regulären Funktionen in C # 5.0

10

In letzter Zeit kann ich anscheinend nicht genug von dem erstaunlichen asynchronen Muster von C # 5.0 bekommen. Wo warst du mein ganzes Leben lang?

Ich bin absolut begeistert von der einfachen Syntax, aber ich habe eine kleine Schwierigkeit. Mein Problem ist, dass asynchrone Funktionen eine völlig andere Deklaration haben als reguläre Funktionen. Da auf andere asynchrone Funktionen nur asynchrone Funktionen warten können, habe ich beim Versuch, alten Blockierungscode auf asynchron zu portieren, einen Dominoeffekt von Funktionen, die ich konvertieren muss.

Die Leute haben dies als Zombie-Befall bezeichnet . Wenn Async einen Biss in Ihren Code bekommt, wird er immer größer. Der Portierungsprozess ist nicht schwierig, er wirft nur asyncdie Deklaration ein und umschließt den Rückgabewert mit Task<>. Es ist jedoch ärgerlich, dies immer wieder zu tun, wenn alter synchroner Code portiert wird.

Es scheint mir viel natürlicher zu sein, wenn beide Funktionstypen (asynchrone und einfache alte Synchronisation) genau dieselbe Syntax hätten. Wenn dies der Fall wäre, würde die Portierung keinen Aufwand erfordern und ich könnte schmerzlos zwischen den beiden Formen wechseln.

Ich denke, das könnte funktionieren, wenn wir diese Regeln befolgen:

  1. Für asynchrone Funktionen ist die asyncDeklaration nicht mehr erforderlich . Ihre Rückgabetypen müssten nicht eingewickelt werden Task<>. Der Compiler erkennt während der Kompilierung selbst eine asynchrone Funktion und führt den Task <> -Wrap automatisch nach Bedarf durch.

  2. Keine Fire-and-Forget-Aufrufe mehr für asynchrone Funktionen. Wenn Sie eine asynchrone Funktion aufrufen möchten, müssen Sie darauf warten. Ich benutze sowieso kaum Feuer und Vergessen, und alle Beispiele für verrückte Rennbedingungen oder Deadlocks scheinen immer darauf zu beruhen. Ich denke, sie sind zu verwirrend und "berührungslos" mit der synchronen Denkweise, die wir zu nutzen versuchen.

  3. Wenn Sie wirklich nicht ohne Feuer und Vergessen leben können, gibt es eine spezielle Syntax dafür. Auf jeden Fall wird es nicht Teil der einfachen einheitlichen Syntax sein, über die ich spreche.

  4. Das einzige Schlüsselwort, das Sie für einen asynchronen Aufruf benötigen, ist await. Wenn Sie gewartet haben, ist der Anruf asynchron. Wenn Sie dies nicht tun, ist der Anruf einfach alt synchron (denken Sie daran, wir haben kein Feuer und Vergessen mehr).

  5. Der Compiler erkennt asynchrone Funktionen automatisch (da sie keine spezielle Deklaration mehr haben). Regel 4 macht dies sehr einfach - wenn eine Funktion einen awaitAufruf enthält, ist sie asynchron.

Könnte das funktionieren? oder fehlt mir etwas Diese einheitliche Syntax ist viel flüssiger und könnte den Zombie-Befall insgesamt lösen.

Einige Beispiele:

// assume this is an async function (has await calls inside)
int CalcRemoteBalanceAsync() { ... }

// assume this is a regular sync function (has no await calls inside)
int CalcRemoteBalance() { ... }

// now let's try all combinations and see what they do:

// this is the common synchronous case - it will block
int b1 = CalcRemoteBalance();

// this is the common asynchronous case - it will not block
int b2 = await CalcRemoteBalanceAsync();

// choosing to call an asynchronous function in a synchronous manner - it will block
// this syntax was used previously for async fire-and-forget, but now it's just synchronous
int b3 = CalcRemoteBalanceAsync();

// strange combination - it will block since it's calling a synchronous function
// it should probably show a compilation warning though
int b4 = await CalcRemoteBalance();

Hinweis: Dies ist eine Fortsetzung einer interessanten verwandten Diskussion in SO

talkol
quelle
3
Sie warten immer auf Ihre asynchronen Operationen? Bitte sagen Sie mir, dass Sie dies nicht sofort nach dem Abfeuern tun ...
Jimmy Hoffa
1
Eines der großartigen Dinge bei Async ist auch, dass Sie nicht awaitsofort müssen. Sie können so etwas tun var task = FooAsync(); Bar(); await task;. Wie würde ich das in Ihrem Vorschlag tun?
Svick
3
SO ist eine Diskussion? Wo ist mein BFG-3000 ...
Robert Harvey
2
@talkol Du denkst, dass parallele Programmierung obszön ist? Das ist, gelinde gesagt, ein interessanter Ausblick, wenn Sie darüber sprechen async. Ich denke, das ist einer der großen Vorteile von async- await: dass Sie auf einfache Weise asynchrone Operationen erstellen können (und das nicht nur auf einfachste Weise "Start A, Warte auf A, Start B, Warte auf B"). Und genau für diesen Zweck gibt es bereits eine spezielle Syntax: Sie heißt await.
Svick
1
@svick haha, jetzt sind wir da :) Ich denke nicht, dass paralleles Prog obszön ist, aber ich denke, es mit asynchronem Warten zu tun ist. Async-await ist syntaktischer Zucker, um Ihren synchronen Geisteszustand aufrechtzuerhalten, ohne den Preis für das Blockieren zu zahlen. Wenn Sie bereits parallel denken, würde ich Sie dringend bitten, ein anderes Muster zu verwenden
talkol

Antworten:

9

Ihre Frage wurde bereits in der von Ihnen verknüpften SO-Frage beantwortet.

Der Zweck von async / await besteht darin, das Schreiben von Code in einer Welt mit vielen Operationen mit hoher Latenz zu vereinfachen. Die überwiegende Mehrheit Ihrer Vorgänge weist keine hohe Latenz auf.

Als WinRT herauskam, beschrieben die Designer, wie sie entschieden, welche Vorgänge asynchron sein sollten. Sie beschlossen, dass alles, was 50 ms oder länger dauern würde, asynchron sein würde und der Rest der Methoden gewöhnliche, nicht asynchrone Methoden sein würden.

Wie viele der Methoden mussten neu geschrieben werden, um sie asynchron zu machen? Etwa 10 Prozent von ihnen. Die anderen 90% waren überhaupt nicht betroffen.

Eric Lippert erklärt ausführlich, warum sie sich entschieden haben, keinen einheitlichen Ansatz zu wählen. Er sagt im Grunde, dass asyncund awaitist eine teilweise Implementierung des Continuation-Passing-Stils, und dass die Optimierung eines solchen Stils für alle Fälle ein schwieriges Problem ist.

Robert Harvey
quelle
Bitte beachten Sie den wesentlichen Unterschied zwischen der SO-Frage und dieser. Der SO fragt, warum nicht alles asynchron gemacht werden soll. Hier schlagen wir das nicht vor, wir schlagen vor, 10% asynchron zu machen, nur die gleiche Syntax dafür zu verwenden, das ist alles. Die Verwendung einer näheren Syntax hat den Vorteil, dass Sie leichter ändern können, welche 10% asynchron sind, ohne Dominoeffekte durch diese Änderungen zu
erleiden
Ich bin mir ein wenig unklar, warum asyncZombiebefall auftreten würde. Selbst wenn eine Methode 10 andere Methoden aufruft, müssen Sie nicht einfach asyncdie oberste Ebene aufrufen?
Robert Harvey
6
Angenommen, 100% meines aktuellen Codes sind synchron. Jetzt habe ich eine einzelne interne Funktion auf Blattebene, die die Datenbank abfragt, die ich in asynchron ändern möchte. Um es asynchron zu machen, muss sein Anrufer asynchron sein und sein Anrufer asynchron sein und so weiter bis zur obersten Ebene. Natürlich spreche ich über den Fall, in dem die gesamte Kette erwartet wird (um das synchrone Design des Codes
beizubehalten