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处结束,如图所示。

相关推荐
腾讯TNTWeb前端团队4 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
mghio6 小时前
Dubbo 中的集群容错
java·微服务·dubbo
范文杰8 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪8 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪8 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy9 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom9 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom9 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom9 小时前
React与Next.js:基础知识及应用场景
前端·面试·github