ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

async和await详解(C#)

2026/8/16 1:51:42 拓冰建站 浏览量
async和await详解(C#)

async、await网上讲解的资料已经有很多了,看似没有什么好讲的,但是我发现很多文章还是从理论上讲解了相关的语法糖,懂得都懂,不懂的还是没有懂。

其实async和await在编译器层面只是语法糖,在IL层面还是会显出原型的;所以,在IL层面深入认识它们还是很有必要的。

从IL层面认识

例如如下代码,使用webclient异步下载“https://editor.csdn.net/md?not_checkout=1&spm=1011.2124.3001.6183&articleId=134696726”的HTML。

public class WebClientClass{public static void Main(string[] args){var html = GetResult();Console.WriteLine("稍等......正在下载");var content = html.Result;Console.WriteLine(content);}static async Task<string> GetResult(){var client = new WebClient();var content = await client.DownloadStringTaskAsync(new Uri("https://editor.csdn.net/md?not_checkout=1&spm=1011.2124.3001.6183&articleId=134696726"));return content;}}

输出结果
在这里插入图片描述
上面的代码非常简单,可以看出异步操作并没有阻塞主线程输出。

挖掘async和await的IL代码

在这里插入图片描述
从上图可以看出,有一个GetResult方法,一个Main方法,还有一个新增加的<GetResult>d__1类;如果你看不懂,没关系,接下来我们逐一进行讲解。

<GetResult>d__1

因为多了一个这样的类,就勾起了我的好奇心,小手已经按捺不住要去挥动鼠标进行探索了,所以先看看它的IL是长什么样的。

.class nested private auto ansi sealed beforefieldinit '<GetResult>d__1'extends [System.Runtime]System.Objectimplements [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine{ 	.method private final hidebysig newslot virtualinstance void MoveNext () cil managed{  }.method private final hidebysig newslot virtualinstance void SetStateMachine (class [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine stateMachine) cil managed{}} 

从上面的IL代码可以看出,是自动生成的<GetResult>d__1类,实现了接口IAsyncStateMachine,这个接口的定义如下:

public interface IAsyncStateMachine
{void MoveNext ();void SetStateMachine (IAsyncStateMachine stateMachine);
}

当你看到这个接口的MoveNext方法是不是似曾相识,但是一时间你又想不起来?没错,就是你平时用foreach集合时,就会使用到这个方法。在foreach集合中被称之为枚举类,但在这里被改造了,称为状态机。

GetResult

继续看IL中的代码

.method private hidebysig static class [System.Runtime]System.Threading.Tasks.Task`1<string> GetResult () cil managed 
{IL_0000: newobj instance void ConsoleApp3.Program/'<GetResult>d__1'::.ctor()IL_0005: stloc.0IL_0006: ldloc.0IL_0007: call valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<!0> valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string>::Create()IL_000c: stfld valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string> ConsoleApp3.Program/'<GetResult>d__1'::'<>t__builder'IL_0011: ldloc.0IL_0012: ldc.i4.m1IL_0013: stfld int32 ConsoleApp3.Program/'<GetResult>d__1'::'<>1__state'IL_0018: ldloc.0IL_0019: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string> ConsoleApp3.Program/'<GetResult>d__1'::'<>t__builder'IL_001e: ldloca.s 0IL_0020: call instance void valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string>::Start<class ConsoleApp3.Program/'<GetResult>d__1'>(!!0&)IL_0025: ldloc.0IL_0026: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string> ConsoleApp3.Program/'<GetResult>d__1'::'<>t__builder'IL_002b: call instance class [System.Runtime]System.Threading.Tasks.Task`1<!0> valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string>::get_Task()IL_0030: ret
} // end of method Program::GetResult

如果你稍微懂一点IL代码,你就应该知道在IL_0000处的newobj,这个方法是做了new <GetResult>d__1;在IL_002b处,返回一个get_Task方法。这时你应该就会明白,为什么主线程不会被阻塞,因为返回的是Task<string>,最后http的结果会在Task<string>中。

将IL代码还原成C#

通过前面一部分的讲解,你应该对async和await在IL层面有一个框架性的认识,这里我将IL代码全部还原成C#代码,看看又有哪些新的发现。

static void Main(string[] args){var html = GetResult();Console.WriteLine("稍等... 正在下载");var content = html.Result;Console.WriteLine(content);}static Task<string> GetResult(){GetResult stateMachine = new GetResult();stateMachine.builder = AsyncTaskMethodBuilder<string>.Create();stateMachine.state = -1;stateMachine.builder.Start(ref stateMachine);return stateMachine.builder.Task;}
public class GetResult : IAsyncStateMachine{public int state;public AsyncTaskMethodBuilder<string> builder;private WebClient client;private string content;private string s3;private TaskAwaiter<string> awaiter;public async void MoveNext(){var result = string.Empty;TaskAwaiter<string> localAwaiter;GetResult stateMachine;int num = state;try{if(num == 0){localAwaiter = awaiter;awaiter = default(TaskAwaiter<string>);num = state = -1;}else{client = new WebClient();localAwaiter = client.DownloadStringTaskAsync(new Uri("https://editor.csdn.net/md?not_checkout=1&spm=1011.2124.3001.6183&articleId=134696726")).GetAwaiter();if (!localAwaiter.IsCompleted){num = state = 0;awaiter = localAwaiter;stateMachine = this;builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);return;}}s3 = localAwaiter.GetResult();content = s3;s3 = null;result = content;}catch (Exception ex){state = -2;client = null;content = null;builder.SetException(ex);}state = -2;client = null;content = null;builder.SetResult(result);}public void SetStateMachine(IAsyncStateMachine stateMachine){throw new NotImplementedException();}}

运行结果和之前一样。

为了方便理解,我将上面的内容整理为一张图。
在这里插入图片描述
它基本流程就是:

stateMachine.builder.Start(ref stateMachine) ->GetResult.MoveNext ->client.DownloadStringTaskAsync -> localAwaiter.IsCompleted = false -> builder.AwaitUnsafeOnCompleted(ref localAwaiter, ref stateMachine)-> GetResult.MoveNext -> localAwaiter.GetResult()-> builder.SetResult(result)

剖析 AsyncTaskMethodBuilder

如果你有仔细的观察,你会发现async和await的异步运作都是由AsyncTaskMethodBuilder承载的。当异步任务启动时,对结果的封送,与底层IO接触,Task对应着AsyncTaskMethodBuilderTask<string>对应着AsyncTaskMethodBuilder<string>。这也是为什么编译器在async处一直提示你返回TaskTask<string>,如果没有TaskTask<string>的返回值,就找不到对应的AsyncTaskMethodBuilder

然后再了解下AwaitUnsafeOnComplete方法,这个方法也是非常重要

public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine> (ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine
{AwaitUnsafeOnCompleted (ref awaiter, ref stateMachine, ref m_task);
}

程序一旦调用这个方法,在底层IO处理任务完毕后,二次回调此GetResult.MoveNext方法,表示异常完成任务。

TaskAwaiter包装Task结果,封送到builder.SetResult中。

当你调试上述从IL转换为C#代码时,MoveNext方法会执行两次。

第一次MoveNext的触发由stateMachine.builder.Start(ref stateMachine)开始,如图所示。
在这里插入图片描述
第二次的MoveNext回调触发由builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine)开始,一旦网络驱动程序处理完毕后,就由线程池IO线程发起到最后触发MoveNext,最后就是执行到awaiter中获取task的result处结束,如图所示。
在这里插入图片描述