Hier, je faisais un exposé sur la nouvelle fonctionnalité C # "async", en particulier sur l'aspect du code généré et the GetAwaiter()
/ BeginAwait()
/ les EndAwait()
appels.
Nous avons examiné en détail la machine d'état générée par le compilateur C #, et il y avait deux aspects que nous ne pouvions pas comprendre:
- Pourquoi la classe générée contient une
Dispose()
méthode et une$__disposing
variable, qui ne semblent jamais être utilisées (et la classe ne l'implémente pasIDisposable
). - Pourquoi la
state
variable interne est définie sur 0 avant tout appel àEndAwait()
, alors que 0 semble normalement signifier "c'est le point d'entrée initial".
Je soupçonne que le premier point pourrait être répondu en faisant quelque chose de plus intéressant dans la méthode asynchrone, bien que si quelqu'un a des informations supplémentaires, je serais heureux de l'entendre. Cette question concerne cependant davantage le deuxième point.
Voici un exemple de code très simple:
using System.Threading.Tasks;
class Test
{
static async Task<int> Sum(Task<int> t1, Task<int> t2)
{
return await t1 + await t2;
}
}
... et voici le code qui est généré pour la MoveNext()
méthode qui implémente la machine d'état. Ceci est copié directement à partir de Reflector - je n'ai pas corrigé les noms de variables indescriptibles:
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);
}
}
C'est long, mais les lignes importantes pour cette question sont les suivantes:
// 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();
Dans les deux cas, l'état est à nouveau modifié par la suite avant d'être ensuite observé de manière évidente ... alors pourquoi le mettre à 0? Si j'étais MoveNext()
appelé à nouveau à ce stade (soit directement, soit via Dispose
), il redémarrerait effectivement la méthode asynchrone, ce qui serait tout à fait inapproprié pour autant que je sache ... si et MoveNext()
n'est pas appelé, le changement d'état n'est pas pertinent.
Est-ce simplement un effet secondaire du compilateur réutilisant le code de génération de bloc d'itérateur pour async, où il peut avoir une explication plus évidente?
Avertissement important
Évidemment, ce n'est qu'un compilateur CTP. Je m'attends à ce que les choses changent avant la version finale - et peut-être même avant la prochaine version CTP. Cette question n'essaie en aucun cas de prétendre qu'il s'agit d'une faille dans le compilateur C # ou quelque chose comme ça. J'essaie juste de savoir s'il y a une raison subtile à cela que j'ai ratée :)