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。

csharp 复制代码
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是长什么样的。

csharp 复制代码
.class nested private auto ansi sealed beforefieldinit '<GetResult>d__1'
 	extends [System.Runtime]System.Object
 	implements [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine
 { 	.method private final hidebysig newslot virtual
  		instance void MoveNext () cil managed
  	{  
        }
      .method private final hidebysig newslot virtual
  		instance void SetStateMachine (
 			class [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine stateMachine
 		) cil managed
      {
      }
 } 

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

csharp 复制代码
public interface IAsyncStateMachine
{
	void MoveNext ();

	void SetStateMachine (IAsyncStateMachine stateMachine);
}

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

GetResult

继续看IL中的代码

csharp 复制代码
.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.0
	IL_0006: ldloc.0
	IL_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.0
	IL_0012: ldc.i4.m1
	IL_0013: stfld int32 ConsoleApp3.Program/'<GetResult>d__1'::'<>1__state'
	IL_0018: ldloc.0
	IL_0019: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string> ConsoleApp3.Program/'<GetResult>d__1'::'<>t__builder'
	IL_001e: ldloca.s 0
	IL_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.0
	IL_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#代码,看看又有哪些新的发现。

csharp 复制代码
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();
        }
    }

运行结果和之前一样。

为了方便理解,我将上面的内容整理为一张图。

它基本流程就是:

csharp 复制代码
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方法,这个方法也是非常重要

csharp 复制代码
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处结束,如图所示。

相关推荐
东风破_17 分钟前
danci 2:创建的单词书到底存在哪里?从 Supabase 一路理解 ORM、Drizzle 和 RLS
数据库·后端·node.js
CoderIsArt23 分钟前
C#中UI 线程与 Dispatcher
开发语言·ui·c#
微尘寒风33 分钟前
【Git】的安装和使用
java·git
东风破_1 小时前
danci 项目(三):从 JSON 数据到 AI Coding,真实项目里的数据清洗、Prompt 和工程规范
前端·后端·node.js
y = xⁿ1 小时前
DeepSeek Harness 学习日记:关于Agent接口,工具调用的底层实现
android·java·学习
东风破_1 小时前
danci 项目(一):从需求到架构,一个单词学习系统为什么会这样设计
前端·后端·node.js
蒲公英eric2 小时前
从客户端到服务端:DVWA DOM 型 XSS 模块完整漏洞分析教程
前端·web安全·ai·xss·dvwa·ai安全
侧耳倾听1112 小时前
jwt使用简介
java·jwt
橙子家2 小时前
OSS 文件上传的几个风险点和解决方案
数据库
单线程_012 小时前
从案例分析 Vue3 Tokenizer+Parser 源码三
前端·javascript·vue.js