Ontem, eu estava dando uma palestra sobre o novo recurso "assíncrono" do C #, investigando em particular como era o código gerado e the GetAwaiter()
/ BeginAwait()
/ EndAwait()
chama.
Examinamos com detalhes a máquina de estado gerada pelo compilador C # e havia dois aspectos que não conseguimos entender:
- Por que a classe gerada contém um
Dispose()
método e uma$__disposing
variável, que nunca parecem ser usados (e a classe não é implementadaIDisposable
). - Por que a
state
variável interna é definida como 0 antes de qualquer chamadaEndAwait()
, quando 0 normalmente parece significar "este é o ponto de entrada inicial".
Suspeito que o primeiro ponto possa ser respondido fazendo algo mais interessante dentro do método assíncrono, embora se alguém tiver mais informações, eu ficaria feliz em ouvi-lo. Esta questão é mais sobre o segundo ponto, no entanto.
Aqui está uma parte muito simples do código de exemplo:
using System.Threading.Tasks;
class Test
{
static async Task<int> Sum(Task<int> t1, Task<int> t2)
{
return await t1 + await t2;
}
}
... e aqui está o código que é gerado para o MoveNext()
método que implementa a máquina de estado. Isso é copiado diretamente do Reflector - não corrigi os nomes indizíveis das variáveis:
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);
}
}
É longo, mas as linhas importantes para esta pergunta são as seguintes:
// 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();
Nos dois casos, o estado é alterado novamente mais tarde antes de ser obviamente observado ... então, por que defini-lo como 0? Se MoveNext()
fosse chamado novamente neste momento (diretamente ou via Dispose
), ele efetivamente iniciaria o método assíncrono novamente, o que seria totalmente inapropriado, tanto quanto eu sei ... se e MoveNext()
não for chamado, a mudança de estado é irrelevante.
Isso é simplesmente um efeito colateral do compilador que reutiliza o código de geração de blocos do iterador para assíncrono, onde pode ter uma explicação mais óbvia?
Isenção de responsabilidade importante
Obviamente, este é apenas um compilador CTP. Espero totalmente que as coisas mudem antes do lançamento final - e possivelmente até antes do próximo lançamento do CTP. Esta questão não está de forma alguma tentando afirmar que esta é uma falha no compilador C # ou algo assim. Eu só estou tentando descobrir se há uma razão sutil para isso que eu perdi :)