.NET 中 Async 方法的内部实现 [第一部分]





5.00/5 (3投票s)
简介 本文探讨 await/async 功能的内部实现。它不涉及高级功能,而是旨在探索内部实现。如果您希望先熟悉整体功能,请参阅 Microsoft 提供的深入文章:
引言
本文探讨 await
/async
功能的内部实现。它不涉及高级功能,而是旨在探索内部实现。如果您希望先熟悉整体功能,请参阅 Microsoft 的深入文章:https://msdn.microsoft.com/en-us/library/hh191443.aspx。
Microsoft 提供的示例图
自从此功能发布以来,我一直好奇它的内部实现,这促使我进行了调查,并撰写了本文。我的结论可能存在一些错误,欢迎以任何形式提供反馈(即使是对我的口头辱骂:P)。
大部分结论都是通过盯着 IL 代码数小时并试图理解它而得出的。我包含了 IL 代码,以便您可以仔细检查我的发现。
Async/Await 概述
Async 功能是在 C# 编译器和 .NET 框架基类库的帮助下实现的。运行时本身无需修改,尽管为了提高 async
/await
的性能,可能进行了一些调整。简单来说,这意味着我们可以在早期版本的 .NET 中借助一些辅助类编写相同的功能,但它不提供向后兼容性。
Stub 方法
当您调用一个 async
方法时,首先返回的是它的 stub。例如,对于此方法
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
的完整实现:http://referencesource.microsoft.com/#mscorlib/system/runtime/compilerservices/AsyncMethodBuilder.cs
在这里,编译器初始化状态机 struct
的各种变量,所有魔术都在状态机中实现。Struct 是一个显而易见的选择,我们不想每次调用该方法时都在堆上创建一个对象,因为这会通过为 GC 创建更多工作来显着降低 async
方法的性能。
编译器使用不受支持的命名约定以确保没有冲突。但是,如果我们修复变量名,在实际实现方面并没有什么新内容,如果我们自己实现辅助类,该代码实际上可以在以前版本的 .NET Framework 中编译。
另一个有趣的观察是,关键字 async
不会影响从外部使用该方法的方式。这就是为什么您无法在接口方法定义中放置 async
关键字,并且允许您使用早期版本的 .NET 实现返回 Task, Task<T>
或 void
的方法。
还有一件事要记住,返回 void
的方法不能被 await。因此,即使您的方法不返回任何内容,也应该返回 Task
,除非您正在编写一个 fire and forget 方法。另一个有趣的事情是,即使我们对字符串调用 ToUpper()
,此实现并未包含在 stub 中。实际实现已移动到状态机。