Gestern hielt ich einen Vortrag über die neue C # "Async" -Funktion, insbesondere über das Aussehen des generierten Codes und the GetAwaiter()
/ BeginAwait()
/ EndAwait()
Aufrufe.
Wir haben uns die vom C # -Compiler generierte Zustandsmaschine genauer angesehen, und es gab zwei Aspekte, die wir nicht verstehen konnten:
- Warum die generierte Klasse eine
Dispose()
Methode und eine$__disposing
Variable enthält, die scheinbar nie verwendet werden (und die Klasse nicht implementiertIDisposable
). - Warum die interne
state
Variable vor jedem Aufruf von aufEndAwait()
0 gesetzt wird, wenn 0 normalerweise "dies ist der erste Einstiegspunkt" bedeutet.
Ich vermute, der erste Punkt könnte durch etwas Interessanteres innerhalb der asynchronen Methode beantwortet werden, obwohl ich mich freuen würde, wenn jemand weitere Informationen hätte. Bei dieser Frage geht es jedoch eher um den zweiten Punkt.
Hier ist ein sehr einfacher Beispielcode:
using System.Threading.Tasks;
class Test
{
static async Task<int> Sum(Task<int> t1, Task<int> t2)
{
return await t1 + await t2;
}
}
... und hier ist der Code, der für die MoveNext()
Methode generiert wird, die die Zustandsmaschine implementiert. Dies wird direkt von Reflector kopiert - ich habe die unaussprechlichen Variablennamen nicht korrigiert:
public void MoveNext()
{
try
{
this.$__doFinallyBodies = true;
switch (this.<>1__state)
{
case 1:
break;
case 2:
goto Label_00DA;
case -1:
return;
default:
this.<a1>t__$await2 = this.t1.GetAwaiter<int>();
this.<>1__state = 1;
this.$__doFinallyBodies = false;
if (this.<a1>t__$await2.BeginAwait(this.MoveNextDelegate))
{
return;
}
this.$__doFinallyBodies = true;
break;
}
this.<>1__state = 0;
this.<1>t__$await1 = this.<a1>t__$await2.EndAwait();
this.<a2>t__$await4 = this.t2.GetAwaiter<int>();
this.<>1__state = 2;
this.$__doFinallyBodies = false;
if (this.<a2>t__$await4.BeginAwait(this.MoveNextDelegate))
{
return;
}
this.$__doFinallyBodies = true;
Label_00DA:
this.<>1__state = 0;
this.<2>t__$await3 = this.<a2>t__$await4.EndAwait();
this.<>1__state = -1;
this.$builder.SetResult(this.<1>t__$await1 + this.<2>t__$await3);
}
catch (Exception exception)
{
this.<>1__state = -1;
this.$builder.SetException(exception);
}
}
Es ist lang, aber die wichtigen Zeilen für diese Frage sind folgende:
// End of awaiting t1
this.<>1__state = 0;
this.<1>t__$await1 = this.<a1>t__$await2.EndAwait();
// End of awaiting t2
this.<>1__state = 0;
this.<2>t__$await3 = this.<a2>t__$await4.EndAwait();
In beiden Fällen wird der Status danach erneut geändert, bevor er das nächste Mal offensichtlich beobachtet wird. Warum also überhaupt auf 0 setzen? Wenn MoveNext()
an dieser Stelle erneut aufgerufen würde (entweder direkt oder über Dispose
), würde die asynchrone Methode effektiv erneut gestartet, was meines Erachtens völlig unangemessen wäre. Wenn und MoveNext()
wird nicht aufgerufen, ist die Zustandsänderung irrelevant.
Ist dies einfach ein Nebeneffekt des Compilers, der den Code zur Generierung von Iteratorblöcken für Async wiederverwendet, wo er möglicherweise eine offensichtlichere Erklärung hat?
Wichtiger Haftungsausschluss
Offensichtlich ist dies nur ein CTP-Compiler. Ich gehe davon aus, dass sich die Dinge vor der endgültigen Veröffentlichung ändern werden - und möglicherweise sogar vor der nächsten CTP-Veröffentlichung. Diese Frage versucht in keiner Weise zu behaupten, dass dies ein Fehler im C # -Compiler oder ähnliches ist. Ich versuche nur herauszufinden, ob es einen subtilen Grund dafür gibt, den ich verpasst habe :)