Questo perché le parole chiave async
e await
sono solo zucchero sintattico per qualcosa chiamato coroutines.
Non ci sono istruzioni IL speciali per supportare la creazione di metodi asincroni. Invece, un metodo asincrono può essere visto come una specie di macchina di stato in qualche modo.
cercherò di fare questo esempio più breve possibile:
[TestClass]
public class AsyncTest
{
[TestMethod]
public async Task RunTest_1()
{
var result = await GetStringAsync();
Console.WriteLine(result);
}
private async Task AppendLineAsync(StringBuilder builder, string text)
{
await Task.Delay(1000);
builder.AppendLine(text);
}
public async Task<string> GetStringAsync()
{
// Code before first await
var builder = new StringBuilder();
var secondLine = "Second Line";
// First await
await AppendLineAsync(builder, "First Line");
// Inner synchronous code
builder.AppendLine(secondLine);
// Second await
await AppendLineAsync(builder, "Third Line");
// Return
return builder.ToString();
}
}
Questo è un codice asincrono come probabilmente avete abituati a: Il nostro metodo GetStringAsync
in un primo momento crea un StringBuilder
sincrono, allora aspetta alcuni metodi asincroni e infine restituisce il risultato. Come sarebbe implementato se non ci fosse la parola chiave await
?
Aggiungere il seguente codice alla classe AsyncTest
:
[TestMethod]
public async Task RunTest_2()
{
var result = await GetStringAsyncWithoutAwait();
Console.WriteLine(result);
}
public Task<string> GetStringAsyncWithoutAwait()
{
// Code before first await
var builder = new StringBuilder();
var secondLine = "Second Line";
return new StateMachine(this, builder, secondLine).CreateTask();
}
private class StateMachine
{
private readonly AsyncTest instance;
private readonly StringBuilder builder;
private readonly string secondLine;
private readonly TaskCompletionSource<string> completionSource;
private int state = 0;
public StateMachine(AsyncTest instance, StringBuilder builder, string secondLine)
{
this.instance = instance;
this.builder = builder;
this.secondLine = secondLine;
this.completionSource = new TaskCompletionSource<string>();
}
public Task<string> CreateTask()
{
DoWork();
return this.completionSource.Task;
}
private void DoWork()
{
switch (this.state)
{
case 0:
goto state_0;
case 1:
goto state_1;
case 2:
goto state_2;
}
state_0:
this.state = 1;
// First await
var firstAwaiter = this.instance.AppendLineAsync(builder, "First Line")
.GetAwaiter();
firstAwaiter.OnCompleted(DoWork);
return;
state_1:
this.state = 2;
// Inner synchronous code
this.builder.AppendLine(this.secondLine);
// Second await
var secondAwaiter = this.instance.AppendLineAsync(builder, "Third Line")
.GetAwaiter();
secondAwaiter.OnCompleted(DoWork);
return;
state_2:
// Return
var result = this.builder.ToString();
this.completionSource.SetResult(result);
}
}
Così, ovviamente, il codice prima del primo await
parola chiave rimane lo stesso. Tutto il resto viene convertito in una macchina a stati che utilizza le istruzioni goto
per eseguire il codice precedente a tratti. Ogni volta che viene completata una delle attività attese, la macchina di stato avanza al passo successivo.
Questo esempio è semplificato per chiarire cosa succede dietro le quinte. Aggiungi la gestione degli errori e alcuni foreach
-Letti nel tuo metodo asincrono e la macchina a stati diventa molto più complessa.
A proposito, c'è un altro costrutto in C# che fa una cosa del genere: la parola chiave yield
. Ciò genera anche una macchina a stati e il codice sembra abbastanza simile a quello prodotto da await
.
Per ulteriori informazioni, esaminare this CodeProject che analizza in modo più approfondito la macchina di stato generata.
Il codice in async/await è basato su [Pattern di enumeratore asincrono di Jeff Richter] (http://channel9.msdn.com/Blogs/Charles/Jeffrey-Richter-and-his-AsyncEnumerator). C'è già una voce che suggerisce una correzione per questo https://ndepend.uservoice.com/forums/226344-ndepend-user-voice/suggestions/6375659-exclude-compiler-generated-code-by-default. – Aron
Perché è così che async/await funziona. Quello che vedi sono i [dettagli dell'implementazione] (http://msdn.microsoft.com/en-us/magazine/hh456402.aspx) della funzione. –
Ciò sottolinea davvero l'importanza di assicurarsi che non si stia usando ciecamente asincrono ovunque quando non è necessario. Questo è più sovraccarico di quello che immagino mi aspettassi. –