在C#语言中,异步编程是一个重要的特性,它允许程序在执行长时间操作时不会阻塞主线程。虽然异步编程的高级功能已经在Microsoft的官方文档中有详细的介绍,但本文旨在探索其内部实现。如果对异步编程的整体功能还不熟悉,可以首先参考Microsoft的深入文章:。
自从异步编程功能发布以来,一直对其内部实现感到好奇,这促使进行了深入的调查研究,从而撰写了本文。在文章中,可能得出了一些错误的结论,因此欢迎任何形式的反馈(即使是对进行口头攻击:P)。
本文中的大多数结论都是通过长时间观察IL代码并尝试理解其含义得出的。已经包含了IL代码,以便可以双重检查发现。
异步功能是由C#编译器与.NET框架基础类库共同实现的。运行时本身不需要修改,尽管完全有可能进行了一些调整以提高async/await的性能。简而言之,这意味着可以使用早期版本的.NET中的一些辅助类自己编写相同的功能,但它不提供向后兼容性。
当调用一个async方法时,首先发生的事情是返回其桩。例如,对于以下方法:
public async Task<string> GetStringAsync(string value)
{
var result = value.ToUpper();
await Task.Delay(1000);
return result;
}
编译器生成以下代码(如果非常好奇,请查看文章末尾的IL文件):
public Task<string> GetStringAsync(string value)
{
AsyncClass.<GetStringAsync>d__0 <GetStringAsync>d__;
<GetStringAsync>d__.<>4__this = this;
<GetStringAsync>d__.value = value;
<GetStringAsync>d__.<>t__builder = AsyncTaskMethodBuilder<string>.Create();
<GetStringAsync>d__.<>1__state = -1;
AsyncTaskMethodBuilder<string> <>t__builder = <GetStringAsync>d__.<>t__builder;
<>t__builder.Start<AsyncClass.<GetStringAsync>d__0>(ref <GetStringAsync>d__);
return <GetStringAsync>d__.<>t__builder.Task;
}
AsyncTaskMethodBuilder的完整实现可以在这里查看:。在这里,编译器初始化状态机结构的各种变量,所有的魔法都在状态机中实现。结构是一个明显的选择,不想每次调用方法时都在堆上创建一个对象,因为这将显著降低异步方法的性能,为垃圾回收器带来更多工作。
编译器使用不支持的命名约定以确保没有冲突。然而,如果修正变量名,在实际实现方面并没有什么新东西,代码实际上可以在以前的.NET Framework版本中编译,只要自己实现辅助类。
另一个有趣的观察是,async关键字对方法的外部使用方式没有影响。这是不能在接口方法定义中使用async关键字的原因之一,并且允许实现使用以前版本的.NET编写的代码,只要它们返回Task, Task