Ieri ho tenuto un discorso sulla nuova funzione "asincrona" di C #, in particolare approfondendo l'aspetto del codice generato e the GetAwaiter()
/ BeginAwait()
/ EndAwait()
chiamate.
Abbiamo esaminato in dettaglio la macchina a stati generata dal compilatore C # e c'erano due aspetti che non potevamo capire:
- Perché la classe generata contiene un
Dispose()
metodo e una$__disposing
variabile, che non sembrano mai essere utilizzati (e la classe non implementaIDisposable
). - Perché la
state
variabile interna è impostata su 0 prima di qualsiasi chiamata aEndAwait()
, quando 0 normalmente significa "questo è il punto di ingresso iniziale".
Sospetto che il primo punto possa essere risolto facendo qualcosa di più interessante nel metodo asincrono, sebbene se qualcuno avesse ulteriori informazioni sarei felice di ascoltarlo. Questa domanda riguarda più il secondo punto, tuttavia.
Ecco un semplice esempio di codice di esempio:
using System.Threading.Tasks;
class Test
{
static async Task<int> Sum(Task<int> t1, Task<int> t2)
{
return await t1 + await t2;
}
}
... ed ecco il codice che viene generato per il MoveNext()
metodo che implementa la macchina a stati. Questo viene copiato direttamente da Reflector - Non ho corretto i nomi delle variabili indicibili:
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);
}
}
È lungo, ma le linee importanti per questa domanda sono queste:
// 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 entrambi i casi lo stato viene nuovamente modificato in seguito prima che venga ovviamente osservato, quindi perché impostarlo su 0? Se MoveNext()
venisse richiamato nuovamente a questo punto (direttamente o tramite Dispose
), si riavvierebbe effettivamente il metodo asincrono, il che sarebbe del tutto inappropriato per quanto posso dire ... se e MoveNext()
non viene chiamato, il cambiamento di stato è irrilevante.
Questo è semplicemente un effetto collaterale del riutilizzo del codice di generazione del blocco iteratore del compilatore per asincrono, dove potrebbe avere una spiegazione più ovvia?
Disclaimer importante
Ovviamente questo è solo un compilatore CTP. Mi aspetto pienamente che le cose cambino prima della versione finale e forse anche prima della prossima versione CTP. Questa domanda non sta affatto cercando di affermare che si tratta di un difetto nel compilatore C # o qualcosa del genere. Sto solo cercando di capire se c'è una ragione sottile per questo che mi sono perso :)