Hier j'ai donné une conférence sur le nouveau C# "async", en particulier à fouiller dans ce que le code généré ressemblait, et the GetAwaiter()
/ BeginAwait()
/ EndAwait()
des appels.
Nous avons examiné en détail l'état de la machine généré par le compilateur C#, et il y avait deux aspects nous ne pouvions pas comprendre:
- Pourquoi la classe générée contient un
Dispose()
méthode et un$__disposing
variable, qui n'apparaissent jamais à être utilisé (et la classe n'implémenteIDisposable
). - Pourquoi l'interne
state
variable est définie à 0, avant tout appel à l'EndAwait()
, lorsque 0 apparaît normalement à dire "c'est le premier point d'entrée".
Je soupçonne le premier point pourrait être répondu en faisant quelque chose de plus intéressant à l'intérieur de la méthode asynchrone, bien que si quelqu'un a des informations je serais heureux de l'entendre. Cette question est plus sur le deuxième point, cependant.
Voici un simple exemple de code:
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 l' MoveNext()
méthode qui implémente l'état de la machine. C'est copié directement à partir de Réflecteur - je n'ai pas fixé jusqu'à l'indicible, les noms de variables:
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);
}
}
Il est long, mais les lignes importants pour cette question sont les suivants:
// 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 modifié à nouveau par la suite avant la prochaine évidemment observé... alors pourquoi le mettre à 0, à tous? Si MoveNext()
ont été appelés de nouveau à ce point (que ce soit directement ou par l'intermédiaire de Dispose
), il serait un moyen efficace de commencer la méthode asynchrone de nouveau, ce qui serait totalement inapproprié pour autant que je peux dire... si et MoveNext()
n'est pas appelée, le changement d'état n'est pas pertinent.
Est-ce simplement un effet secondaire de l'compilateur réutilisation itérateur bloc de génération de code asynchrone, où il peut avoir un plus évident explication?
Avertissement Important
Évidemment, c'est juste un CTP compilateur. Je m'attends à ce que les choses changent avant la version finale et peut-être même avant la prochaine version CTP. Cette question est en aucun cas tenter de prétendre c'est une faille dans le compilateur C# ou quelque chose comme ça. Je suis juste en train de travailler si il y a une subtile raison de ce que j'ai manqué :)