Dies ist, weil die async
und await
Schlüsselwörter sind nur syntaktische Zucker für etwas namens coroutines.
Es gibt keine speziellen IL-Anweisungen zur Unterstützung der Erstellung asynchroner Methoden. Stattdessen kann eine asynchrone Methode irgendwie als eine Art Zustandsmaschine angesehen werden.
Ich werde versuchen, dieses Beispiel so kurz wie möglich zu machen:
[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();
}
}
Dieses einig Asynchron-Code ist wie Sie wahrscheinlich gewöhnt haben: Unsere GetStringAsync
Methode zunächst erzeugen ein StringBuilder
synchron, dann wartet es einige asynchrone Methoden und schließlich gibt es das Ergebnis zurück. Wie würde dies implementiert werden, wenn es kein await
Schlüsselwort gibt?
Fügen Sie den folgenden Code in die AsyncTest
Klasse:
[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);
}
}
So offensichtlich der Code vor dem ersten await
Schlüsselwort bleibt genau das gleiche. Alles andere wird in eine Zustandsmaschine umgewandelt, die goto
Anweisungen verwendet, um Ihren vorherigen Code stückweise auszuführen. Jedes Mal, wenn eine der erwarteten Aufgaben abgeschlossen ist, geht die Zustandsmaschine zum nächsten Schritt über.
Dieses Beispiel ist zu vereinfacht, um zu verdeutlichen, was hinter den Kulissen geschieht. Fügen Sie Fehlerbehandlung und einige foreach
-Loops in Ihrer asynchronen Methode hinzu, und die Zustandsmaschine wird viel komplexer.
Übrigens gibt es ein anderes Konstrukt in C#, das so etwas tut: das Schlüsselwort yield
. Dies erzeugt auch eine Zustandsmaschine und der Code sieht sehr ähnlich aus, was await
erzeugt.
Weitere Informationen finden Sie unter this CodeProject, die einen tieferen Einblick in die generierte Zustandsmaschine gibt.
Der Code in async/await basiert auf [Jeff Richters Async Enumerator-Muster] (http://channel9.msdn.com/Blogs/Charles/Jeffrey-Richter-and-his-AsyncEnumerator). Es gibt bereits eine Stimme, die einen Fix für dieses https://ndepend.uservoice.com/forums/226364-ndepend-user-voice/suggestions/6375659-exclude-compiler-generated-code-by-default vorschlägt. – Aron
Denn so funktioniert async/wait. Was Sie sehen, ist das [Implementierungsdetail] (http://msdn.microsoft.com/en-us/magazine/hh456402.aspx) des Features. –
Dies unterstreicht wirklich die Wichtigkeit, sicherzustellen, dass Sie nicht überall blind async verwenden, wenn es nicht benötigt wird. Das ist mehr Aufwand als das, was ich erwartet habe. –