Ayer estaba dando una charla sobre la nueva función "asíncrona" de C #, en particular profundizando en el aspecto del código generado y the GetAwaiter()
/ BeginAwait()
/ EndAwait()
llamadas.
Observamos con cierto detalle la máquina de estado generada por el compilador de C #, y había dos aspectos que no podíamos entender:
- Por qué la clase generada contiene un
Dispose()
método y una$__disposing
variable, que nunca parecen usarse (y la clase no se implementaIDisposable
). - Por qué la
state
variable interna se establece en 0 antes de cualquier llamada aEndAwait()
, cuando 0 normalmente parece significar "este es el punto de entrada inicial".
Sospecho que el primer punto podría responderse haciendo algo más interesante dentro del método asíncrono, aunque si alguien tiene más información, me alegraría escucharlo. Sin embargo, esta pregunta es más sobre el segundo punto.
Aquí hay una pieza muy simple de código de muestra:
using System.Threading.Tasks;
class Test
{
static async Task<int> Sum(Task<int> t1, Task<int> t2)
{
return await t1 + await t2;
}
}
... y aquí está el código que se genera para el MoveNext()
método que implementa la máquina de estados. Esto se copia directamente desde Reflector. No he arreglado los nombres 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);
}
}
Es largo, pero las líneas importantes para esta pregunta son estas:
// 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();
En ambos casos, el estado cambia nuevamente antes de que se observe de manera obvia ... entonces, ¿por qué establecerlo en 0? Si MoveNext()
se volviera a llamar en este punto (ya sea directamente o por medio Dispose
), volvería a iniciar efectivamente el método asíncrono, lo que sería totalmente inapropiado por lo que puedo decir ... si MoveNext()
se llama y no se llama, el cambio de estado es irrelevante.
¿Es esto simplemente un efecto secundario de que el compilador reutiliza el código de generación de bloque de iterador para asíncrono, donde puede tener una explicación más obvia?
Descargo de responsabilidad importante
Obviamente esto es solo un compilador CTP. Espero que las cosas cambien antes de la versión final, y posiblemente incluso antes de la próxima versión de CTP. Esta pregunta de ninguna manera trata de afirmar que esto es una falla en el compilador de C # o algo así. Solo estoy tratando de averiguar si hay una razón sutil para esto que me haya perdido :)