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

相关推荐
neter.asia4 分钟前
vue中如何关闭eslint检测?
前端·javascript·vue.js
~甲壳虫5 分钟前
说说webpack中常见的Plugin?解决了什么问题?
前端·webpack·node.js
IT技术分享社区12 分钟前
C#实战:使用腾讯云识别服务轻松提取火车票信息
开发语言·c#·云计算·腾讯云·共识算法
桀桀桀桀桀桀14 分钟前
数据库中的用户管理和权限管理
数据库·mysql
代码之光_198022 分钟前
保障性住房管理:SpringBoot技术优势分析
java·spring boot·后端
光影少年24 分钟前
vue2与vue3的全局通信插件,如何实现自定义的插件
前端·javascript·vue.js
As977_25 分钟前
前端学习Day12 CSS盒子的定位(相对定位篇“附练习”)
前端·css·学习
susu108301891127 分钟前
vue3 css的样式如果background没有,如何覆盖有background的样式
前端·css
ajsbxi28 分钟前
苍穹外卖学习记录
java·笔记·后端·学习·nginx·spring·servlet
Ocean☾29 分钟前
前端基础-html-注册界面
前端·算法·html