C# Under the Hood: async/await (Marko Papic)
https://www.markopapic.com/csharp-under-the-hood-async-await/
Async and await keywords came with C# 5 as a cool new feature for handling asynchronous tasks.
They allow us to specify tasks to be executed asynchronously in an easy and straightforward fashion.
However, some people are mystified by asynchronous programming and are not sure how it actually works.
I will try to give you an insight of the magic that happens under the hood when async and await are used.
Awaiter Pattern
C# language compiles some of the features as a syntactic sugar, which means that certain language features are just conveniences that translate into existing language features.
A lot of those syntactic sugar features expand into patterns.Those patterns are based on method calls, property lookups or interface implementations.
await expression is one of those syntactic sugar features. It leverages a pattern based on a few method calls. In order for a type to be awaitable, it has to meet the following requirements:
- It has to have the following method:
INotifyCompletion GetAwaiter() - Besides implementing the INotifyCompletion interface, the return type of the
GetAwaitermethod has to have the following:IsCompletedproperty of typeboolGetResult()method which returnsvoid
If you take a look at Task class, you will see that it meets all the above requirements.
1.被Await的class需要有一个GetAwaiter的方法
2.并且GetAwaiter方法的返回类型,还有3个要求
2.1 必须实现INotifyCompletion接口
2.2 必须有一个IsCompleted属性,类型是bool
2.3 必须有一个GetResult方法,返回值类型是void
So, a type doesn’t even need to implement some specific interface in order to be awaitable.
It just has to have a method with a specific signature. It is similar to duck typing.
If it walks like a duck and it quacks like a duck, then it must be a duck.
In this case it is
If it has certain methods with certain signatures, then it has to be awaitable.
To give you an illustrative example of this, I will create some custom class and make it awaitable.
So, here is my class:
public class MyAwaitableClass
{
}
When I try to await an object of MyAwaitableClass type, I get the following error:
static async Task AwaitMyAwaitable()
{
MyAwaitableClass class1 = new MyAwaitableClass();
await class1; }
Severity Code Description Project File Line Suppression State
Error CS1061 'MyAwaitableClass' does not contain a definition for 'GetAwaiter' and no accessible extension method 'GetAwaiter' accepting a first argument of type 'MyAwaitableClass' could be found (are you missing a using directive or an assembly reference?) async-await-test C:\repository\GitHub\ChuckLu\Test\async-await-test\async-await-test\MainTest.cs 22 Active
Let’s add GetAwaiter method to our class:
public class MyAwaitableClass
{
public MyAwaiter GetAwaiter()
{
return new MyAwaiter();
}
} public class MyAwaiter
{
public bool IsCompleted
{
get { return false; }
}
}
We can see that the compiler error changed:
Severity Code Description Project File Line Suppression State
Error CS0535 'MyAwaiter' does not implement interface member 'INotifyCompletion.OnCompleted(Action)' async-await-test C:\repository\GitHub\ChuckLu\Test\async-await-test\async-await-test\MyAwaitableClass.cs 6 Active
Ok, let’s create implement the INotifyCompletion interface in MyAwaiter:
public class MyAwaiter : INotifyCompletion
{
public bool IsCompleted
{
get { return false; }
} public void OnCompleted(Action continuation)
{
}
}
and see what the compiler error looks like now:
Severity Code Description Project File Line Suppression State
Error CS0117 'MyAwaiter' does not contain a definition for 'GetResult' async-await-test C:\repository\GitHub\ChuckLu\Test\async-await-test\async-await-test\MainTest.cs 18 Active
So, we add a GetResult method and now we have the following:
public class MyAwaitableClass
{
public MyAwaiter GetAwaiter()
{
return new MyAwaiter();
}
} public class MyAwaiter : INotifyCompletion
{
public void GetResult()
{
} public bool IsCompleted
{
get { return false; }
} //From INotifyCompletion
public void OnCompleted(Action continuation)
{
}
}
And we can also see that there are no compiler errors,
which means we have made an awaitable type.
Now that we know which pattern does the await expression leverage, we can take a look under the hood to see what actually happens when we use async and await.
Async
For every async method a state machine is generated.
This state machine is a struct that implements IAsyncStateMachine interface from System.Runtime.CompilerServicesnamespace. This interface is intended for compiler use only and has the following methods:
MoveNext()- Moves the state machine to its next state.SetStateMachine(IAsyncStateMachine)- Configures the state machine with a heap-allocated replica.
Now let’s take a look at the following code:
class Program
{
static void Main(string[] args)
{
} static async Task FooAsync()
{
Console.WriteLine("Async method that doesn't have await");
}
}
We have an async method named FooAsync. You may notice that it lacks await operator, but I left it out for now for the sake of simplicity.
Now let’s take a look at the compiler generated code for this method. I am using dotPeek to decompile the containing .dll file. To see what is going on behind the scenes, you need to enable Show Compiler-generated Code option in dotPeek.
Compiler generated classes usually contain < and > in their names
which are not valid C# identifiers so they don’t conflict with
user-created artifacts.
也可以用dnSpy查看反编译的代码
Let’s take a look what compiler generated for our FooAsync method:

用ILSpy查看,在选项里面,去掉反编译async methods,确保看到状态机的代码


Our Program class contains Main and FooAsync methods as expected, but we can also see that compiler generated a struct called Program.<FooAsync>d__1.
That struct is a state machine that implements the IAsyncStateMachine interface. Besides the IAsyncStateMachine interface methods, this struct also has the following fields:
<>1__statewhich indicates the current state of the state machine<>t__builderof typeAsyncTaskMethodBuilderwhich is used for creation of asynchronous methods and returning the resulting task. TheAsyncTaskMethodBuilderstruct is also intended for use by the compiler.
We will see the code of this struct in more detail, but first let’s take a look at what compiler-generated FooAsync method looks like after we decompiled it:
[AsyncStateMachine(typeof(<FooAsync>d__1))]
[DebuggerStepThrough]
private static Task FooAsync()
{
<FooAsync>d__1 stateMachine = new <FooAsync>d__1();
stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
stateMachine.<>1__state = -;
AsyncTaskMethodBuilder <>t__builder = stateMachine.<>t__builder;
<>t__builder.Start(ref stateMachine);
return stateMachine.<>t__builder.Task;
}
This is what compiler transforms async methods to. The code inside the method does the following:
- Instantiate the method’s state machine
- Create new
AsyncTaskMethodBuilderand set it as state machine’s builder - Set the state machine to a starting state
- Start the builder with the method’s state machine by calling the Start method.
- Return the Task
As you can notice, compiler-generated FooAsync method doesn’t contain any of the code our original FooAsync method had. That code represented the functionality of the method. So where is that code?
That code is moved to state machine’s MoveNext method.
Let’s take a look at Program.<FooAsync>d_1 struct now:
[CompilerGenerated]
private sealed class <FooAsync>d__1 : IAsyncStateMachine
{
public int <>1__state; public AsyncTaskMethodBuilder <>t__builder; private void MoveNext()
{
int num = <>1__state;
try
{
Console.WriteLine("Async method that doesn't have await");
}
catch (Exception exception)
{
<>1__state = -;
<>t__builder.SetException(exception);
return;
}
<>1__state = -;
<>t__builder.SetResult();
} void IAsyncStateMachine.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
this.MoveNext();
} [DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine stateMachine)
{
} void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
{
//ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
this.SetStateMachine(stateMachine);
}
}
The MoveNext method contains method’s code inside of a try block.
If some exception occurs in our code, it will be given to the method builder which propagates it all the way to the task.
After that, it uses method builder’s SetResult method to indicate that the task is completed.
Now we saw how async methods look under the hood.
For the sake of simplicity, I didn’t put any await inside of the FooAsync method, so our state machine didn’t have a lot of state transitions.
It just executed our method and went to a completed state, i.e. our method executed synchronously.
Now it is time to see how MoveNext method looks like when a method awaits some task inside of its body.
Await
Let’s take a look at the following method:
static async Task BarAsync()
{
Console.WriteLine("This happens before await"); int i = await QuxAsync(); Console.WriteLine("This happens after await. The result of await is " + i);
}
static async Task BarAsync()
{
Console.WriteLine("This happens before await"); HttpClient httpClient = new HttpClient();
Uri uri = new Uri("https://www.cnblogs.com/chucklu/");
var result = await httpClient.GetAsync(uri);
var statusCode = result.StatusCode;
Console.WriteLine($"This happens after await. The result of await is {statusCode}");
}
It awaits some QuxAsync method and uses its task result.
If we decompile it using dotPeek, we will notice that the compiler generated method has the same structure as FooAsync even if the original methods are different:
[AsyncStateMachine(typeof(<BarAsync>d__2))]
[DebuggerStepThrough]
private static Task BarAsync()
{
<BarAsync>d__2 stateMachine = new <BarAsync>d__2();
stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
stateMachine.<>1__state = -;
AsyncTaskMethodBuilder <>t__builder = stateMachine.<>t__builder;
<>t__builder.Start(ref stateMachine);
return stateMachine.<>t__builder.Task;
}
What makes the difference is the state machine’s MoveNext method.
Now that we have an await expression inside of our method, the state machine loks like this:
[CompilerGenerated]
private sealed class <BarAsync>d__2 : IAsyncStateMachine
{
public int <>1__state; public AsyncTaskMethodBuilder <>t__builder; private HttpClient <httpClient>5__1; private Uri <uri>5__2; private HttpResponseMessage <result>5__3; private HttpStatusCode <statusCode>5__4; private HttpResponseMessage <>s__5; private TaskAwaiter<HttpResponseMessage> <>u__1; private void MoveNext()
{
int num = <>1__state; //num variable holds the state
try
{
TaskAwaiter<HttpResponseMessage> awaiter;
if (num != )// The first state is -1(BarAsync method sets it to that value before starting the builder), so it will pass this check in the first pass
{
Console.WriteLine("This happens before await");
<httpClient>5__1 = new HttpClient();
<uri>5__2 = new Uri("https://www.cnblogs.com/chucklu/");
awaiter = <httpClient>5__1.GetAsync(<uri>5__2).GetAwaiter();
if (!awaiter.IsCompleted) //check if the asynchronous operation has already completed synchronously by the time we awaited
{
//in case the method has not yet completed, do the following:
//1. set the state to 0(so next time it will not pass the check and go to else block below)
//2. register a callback using AwaitUnsafeOnCompleted, which eventually call the MoveNext method when the task completes
//3. return to prevent further execution
num = (<>1__state = );
<>u__1 = awaiter;
<BarAsync>d__2 stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
return;
}
}
else
{
awaiter = <>u__1;
<>u__1 = default(TaskAwaiter<HttpResponseMessage>);
num = (<>1__state = -);
}
<>s__5 = awaiter.GetResult();
<result>5__3 = <>s__5;
<>s__5 = null;
<statusCode>5__4 = <result>5__3.StatusCode;
//The following line gets to be executed in the following case:
//1. The asynchronous operation has already completed by the time !task.Completed check happened
//2. The asynchronous operation has completed and MoveNext method is invoked as a callback. In this case state is 0, num!=0 check returns false
//It gets the task result and executes the rest of the method
Console.WriteLine($"This happens after await. The result of await is {<statusCode>5__4}");
}
catch (Exception exception)
{
<>1__state = -;
<>t__builder.SetException(exception);
return;
}
<>1__state = -;
<>t__builder.SetResult();
} void IAsyncStateMachine.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
this.MoveNext();
} [DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine stateMachine)
{
} void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
{
//ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
this.SetStateMachine(stateMachine);
}
}
The above red word contains an explanation of the above state machine:
So, what await actually does is the following:
下面的num==0判断是No的话,意味着第一次进入MoveNext

Summary
Every time you create an async method, the compiler generates a state machine for it. Then for each await inside of that method, it does the following:
- Executes the method to the
awaitexpression - Checks if the method being awaited has already completed
- If yes, executes the rest of the method
- If no, uses callback to execute the rest of the method when the method being awaited completes
C# Under the Hood: async/await (Marko Papic)的更多相关文章
- C# async await and state machine
Async Await and the Generated StateMachine https://www.codeproject.com/Articles/535635/Async-Await-a ...
- C# async await 死锁问题总结
可能发生死锁的程序类型 1.WPF/WinForm程序 2.asp.net (不包括asp.net mvc)程序 死锁的产生原理 对异步方法返回的Task调用Wait()或访问Result属性时,可能 ...
- async/await 内幕【译文】
C# Under the Hood: async/await 原文地址:https://www.markopapic.com/csharp-under-the-hood-async-await/ 前言 ...
- async & await 的前世今生(Updated)
async 和 await 出现在C# 5.0之后,给并行编程带来了不少的方便,特别是当在MVC中的Action也变成async之后,有点开始什么都是async的味道了.但是这也给我们编程埋下了一些隐 ...
- [.NET] 利用 async & await 的异步编程
利用 async & await 的异步编程 [博主]反骨仔 [出处]http://www.cnblogs.com/liqingwen/p/5922573.html 目录 异步编程的简介 异 ...
- [.NET] 怎样使用 async & await 一步步将同步代码转换为异步编程
怎样使用 async & await 一步步将同步代码转换为异步编程 [博主]反骨仔 [出处]http://www.cnblogs.com/liqingwen/p/6079707.html ...
- [.NET] 利用 async & await 进行异步 IO 操作
利用 async & await 进行异步 IO 操作 [博主]反骨仔 [出处]http://www.cnblogs.com/liqingwen/p/6082673.html 序 上次,博主 ...
- [C#] 走进异步编程的世界 - 开始接触 async/await
走进异步编程的世界 - 开始接触 async/await 序 这是学习异步编程的入门篇. 涉及 C# 5.0 引入的 async/await,但在控制台输出示例时经常会采用 C# 6.0 的 $&qu ...
- ASP.NET 中的 Async/Await 简介
本文转载自MSDN 作者:Stephen Cleary 原文地址:https://msdn.microsoft.com/en-us/magazine/dn802603.aspx 大多数有关 async ...
随机推荐
- CVE-2019-0214: Apache Archiva arbitrary file write and delete on the server
CVE-2019-0214: Apache Archiva arbitrary file write and delete on the server Severity: Medium Vendor: ...
- linux cenos开放端口
问题:8080端口不能访问 解决方案: 第1步 查看阿里云端口是否开放 网络安全>安全组>配置规则>添加入方向 第二步 查看防火墙是否开启(只说开启了防火墙的情况) 查看防火墙状态: ...
- You're currently running Fcitx with GUI 错误解决 Fcitx
在英文版ubuntu配置输入法时,点击 Configure Current Input Method 会报以下的错误: You’re currently running Fcitx with GUI, ...
- H5之postMessage
对于跨域我们有很多的解决方案,今天我来分享一下postMessage的那点事,postMessage是html5新增的一个解决跨域的一个方法,不过很可惜万恶的ie6,7不支持 postMessage( ...
- 用js刷剑指offer(变态跳台阶)
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级.求该青蛙跳上一个n级的台阶总共有多少种跳法. 牛客网链接 思路 假设青蛙跳上一个n级的台阶总共有f(n)种跳法. 现在青蛙从第n个台阶 ...
- Http 缓存剖析
缓存一直是前端优化的主战场, 利用好缓存就成功了一半. 本篇从http请求和响应的头域入手, 让你对浏览器缓存有个整体的概念. 最终你会发现强缓存, 协商缓存 和 启发式缓存是如此的简单. 导读 浏览 ...
- Linux如何杀掉tty终端
今天工作中遇到了同事的终端登陆不上去的问题,尝试着如何解决,首先想到的就是先干掉tty终端. 下面是总结的如何杀掉tty终端: 1.使用w命令查看当前登陆的用户及使用的tty [root@host ~ ...
- confluent_kafka消费时内存泄漏
confluent_kafka测试的内存泄漏的条件 多线程消费 centos6 预测和centos6底层库存在关系. 换用centos7(我是换了7.3)就行了. (起初以为是代码问题,定位问题位置后 ...
- Java内存模型与垃圾回收笔记
内存模型 栈. 局部变量(基本类型)与对象引用:线程隔离.每个方法执行时会创建一个栈帧,存储局部变量等. 堆. 对象实例:线程共享. 方法区.类信息.常量(final).静态变量.符号引用: 线程共享 ...
- BZOJ 2281: [Sdoi2011]黑白棋 (Nim游戏+dp计数)
题意 这题目有一点问题,应该是在n个格子里有k个棋子,k是偶数.从左到右一白一黑间隔出现.有两个人不妨叫做小白和小黑.两个人轮流操作,每个人可以选 1~d 枚自己颜色的棋子,如果是白色则只能向右移动, ...