OK, hier ist meine Einstellung dazu.
Es gibt so etwas wie Koroutinen , die seit Jahrzehnten bekannt sind. ("Knuth and Hopper" -Klasse "seit Jahrzehnten") Sie sind Verallgemeinerungen von Unterprogrammen , indem sie nicht nur die Kontrolle über den Funktionsstart und die Rückgabeanweisung erhalten und freigeben, sondern dies auch an bestimmten Punkten ( Suspendierungspunkten ). Eine Subroutine ist eine Coroutine ohne Aufhängepunkte.
Sie sind EINFACH mit C-Makros zu implementieren, wie im folgenden Artikel über "Protothreads" gezeigt. ( http://dunkels.com/adam/dunkels06protothreads.pdf ) Lesen Sie es. Ich werde warten...
Unterm Strich dafür ist , dass die Makros ein großen erstellen switch
, und ein case
Etikett an jedem Aufhängungspunkt. An jedem Unterbrechungspunkt speichert die Funktion den Wert des unmittelbar folgenden case
Etiketts, damit sie weiß, wo die Ausführung beim nächsten Aufruf fortgesetzt werden soll. Und es gibt die Kontrolle an den Anrufer zurück.
Dies geschieht ohne Änderung des scheinbaren Kontrollflusses des im "Protothread" beschriebenen Codes.
Stellen Sie sich vor, Sie haben eine große Schleife, die alle diese "Protothreads" der Reihe nach aufruft und gleichzeitig "Protothreads" für einen einzelnen Thread ausführt.
Dieser Ansatz hat zwei Nachteile:
- Sie können den Status in lokalen Variablen zwischen den Wiederaufnahmen nicht beibehalten.
- Sie können den "Protothread" nicht von einer beliebigen Aufruftiefe aussetzen. (alle Aufhängepunkte müssen auf Level 0 sein)
Es gibt Problemumgehungen für beide:
- Alle lokalen Variablen müssen in den Kontext des Protothreads gezogen werden (Kontext, der bereits benötigt wird, weil der Protothread seinen nächsten Wiederaufnahmepunkt speichern muss)
- Wenn Sie der Meinung sind, dass Sie wirklich einen anderen Protothread von einem Protothread aufrufen müssen, "laichen" Sie einen untergeordneten Protothread und setzen Sie ihn aus, bis das untergeordnete Element fertig ist.
Und wenn Sie Compiler-Unterstützung hätten, um die Umschreibungsarbeit zu erledigen, die die Makros und die Problemumgehung leisten, könnten Sie einfach Ihren Protothread-Code so schreiben, wie Sie beabsichtigen, und Suspendierungspunkte mit einem Schlüsselwort einfügen.
Und genau darum geht es async
und await
darum, (stapellose) Koroutinen zu erstellen.
Die Coroutinen in C # werden als Objekte der Klasse (generisch oder nicht generisch) geändert Task
.
Ich finde diese Keywords sehr irreführend. Meine gedankliche Lektüre ist:
async
als "suspensible"
await
als "Aussetzen bis zum Abschluss von"
Task
als "Zukunft ..."
Jetzt. Müssen wir die Funktion wirklich markieren async
? Abgesehen davon, dass es die Mechanismen zum Umschreiben von Code auslösen sollte, um die Funktion zu einer Koroutine zu machen, werden einige Unklarheiten gelöst. Betrachten Sie diesen Code.
public Task<object> AmIACoroutine() {
var tcs = new TaskCompletionSource<object>();
return tcs.Task;
}
Vorausgesetzt, das async
ist nicht obligatorisch, ist dies eine Koroutine oder eine normale Funktion? Sollte der Compiler es als Coroutine umschreiben oder nicht? Beides könnte mit unterschiedlicher Semantik möglich sein.