这是因为
async
和
await
关键字只是一种称为
协程的东西的语法糖。
没有特殊的IL指令支持异步方法的创建。相反,异步方法可以被视为一种状态机。
我将尽可能简短地举一个例子:
[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()
{
var builder = new StringBuilder();
var secondLine = "Second Line";
await AppendLineAsync(builder, "First Line");
builder.AppendLine(secondLine);
await AppendLineAsync(builder, "Third Line");
return builder.ToString();
}
}
这是一些异步代码,您可能已经习惯了:我们的GetStringAsync
方法首先同步创建一个StringBuilder
,然后等待一些异步方法,最后返回结果。如果没有await
关键字,该如何实现呢?
将以下代码添加到AsyncTest
类中:
[TestMethod]
public async Task RunTest_2()
{
var result = await GetStringAsyncWithoutAwait();
Console.WriteLine(result);
}
public Task<string> GetStringAsyncWithoutAwait()
{
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;
var firstAwaiter = this.instance.AppendLineAsync(builder, "First Line")
.GetAwaiter();
firstAwaiter.OnCompleted(DoWork);
return;
state_1:
this.state = 2;
this.builder.AppendLine(this.secondLine);
var secondAwaiter = this.instance.AppendLineAsync(builder, "Third Line")
.GetAwaiter();
secondAwaiter.OnCompleted(DoWork);
return;
state_2:
var result = this.builder.ToString();
this.completionSource.SetResult(result);
}
}
很明显,在第一个
await
关键字之前的代码保持不变。其他所有内容都被转换为状态机,使用
goto
语句逐步执行以前的代码。每当完成一个等待任务时,状态机就会进入下一步。
这个例子过于简单,以阐明幕后发生的情况。在异步方法中添加错误处理和一些
foreach
循环,那么状态机将变得更加复杂。
顺便说一下,C# 中还有另一种结构也可以做到这样的事情:
yield
关键字。它也生成一个状态机,代码看起来与
await
生成的非常相似。
了解更深入的内容,请查看
此 CodeProject,它深入研究了生成的状态机。
async
是基于Async Enumerator的。虽然它们乍一看很相似,但async
能够进行更复杂的转换。 - Stephen Cleary