如果我创建一个简单的类,如下所示:
public class TestClass { public Task TestMethod(int someParameter) { return Task.FromResult(someParameter); } public async Task TestMethod(bool someParameter) { await Task.FromResult(someParameter); } }
并在NDepend中检查它,它显示了使用bool和async Task的TestMethod具有使用枚举器生成的结构体,枚举器状态机和一些附加的东西.
为什么编译器使用async方法的枚举器生成一个名为TestClass&TestMethod> d__0的结构体?
它似乎比实际的方法产生更多的IL.在本示例中,编译器为我的类生成35行IL,同时为该结构生成81行IL.这也增加了编译代码的复杂性,并导致NDepend将其标记为违规违规.
解决方法
这是因为异步和等待关键字只是语法糖,称为
coroutines.
没有特殊的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() { // 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(); } }
这是一些异步代码,您可能习惯了:我们的GetStringAsync方法首先同步创建一个StringBuilder,然后等待一些异步方法,最后返回结果.如果没有等待关键字,将如何实现?
[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); } }
显然,第一个等待关键字之前的代码保持不变.一切都转换成一个状态机,它使用goto语句来分段执行你的前一个代码.每次等待完成的任务,状态机进入下一步.
这个例子过于简单,以澄清幕后发生的情况.在异步方法中添加错误处理和一些foreach-Loops,状态机变得复杂得多.
顺便说一下,C#中有另外一个构造就是这样做:yield关键字.这也产生一个状态机,代码看起来与等待产生的相似.
要进一步阅读,请查看this CodeProject,深入了解生成的状态机.