65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2015 年 2 月 22 日

CPL

3分钟阅读

viewsIcon

16798

简介 本文探讨 await/async 功能的内部实现。它不涉及高级功能,而是旨在探索内部实现。如果您希望先熟悉整体功能,请参阅 Microsoft 提供的深入文章:

引言

本文探讨 await/async 功能的内部实现。它不涉及高级功能,而是旨在探索内部实现。如果您希望先熟悉整体功能,请参阅 Microsoft 的深入文章:https://msdn.microsoft.com/en-us/library/hh191443.aspx

Microsoft 提供的示例图

asyncawait


自从此功能发布以来,我一直好奇它的内部实现,这促使我进行了调查,并撰写了本文。我的结论可能存在一些错误,欢迎以任何形式提供反馈(即使是对我的口头辱骂: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;
}

 

 

在这里,编译器初始化状态机 struct 的各种变量,所有魔术都在状态机中实现。Struct 是一个显而易见的选择,我们不想每次调用该方法时都在堆上创建一个对象,因为这会通过为 GC 创建更多工作来显着降低 async 方法的性能。

编译器使用不受支持的命名约定以确保没有冲突。但是,如果我们修复变量名,在实际实现方面并没有什么新内容,如果我们自己实现辅助类,该代码实际上可以在以前版本的 .NET Framework 中编译。

另一个有趣的观察是,关键字 async 不会影响从外部使用该方法的方式。这就是为什么您无法在接口方法定义中放置 async 关键字,并且允许您使用早期版本的 .NET 实现返回 Task, Task<T>void 的方法。

还有一件事要记住,返回 void 的方法不能被 await。因此,即使您的方法不返回任何内容,也应该返回 Task,除非您正在编写一个 fire and forget 方法。另一个有趣的事情是,即使我们对字符串调用 ToUpper(),此实现并未包含在 stub 中。实际实现已移动到状态机。

AsyncClass.il

© . All rights reserved.