【C# TAP 异步编程】三、async\await的运作机理详解

本文只是个人笔记,很多错误,欢迎指出。

async\await的运作机理

通过分析一段代码运行来了解async\await的运作机理

namespace MyTask;
class Program
{
    public static void Main(string[] args)
    {
        Task<int> baconTask = GetUrlContentLengthAsync(3);
        baconTask.ContinueWith(t => Console.WriteLine(t.Result));
        Console.Read();
    }
    static async Task<int> GetUrlContentLengthAsync(int slices)
    {
        HttpClient httpClient = new();

        //********* 也可以合并起来写*********//
       // string content = await httpClient.GetStringAsync("https://www.cnblogs.com/majiang/p/7899202.html");
  
         Task<string> getContent=  httpClient.GetStringAsync("https://www.cnblogs.com/majiang/p/7899202.html");
          string content = await getContent;
        //await getContent反编译后是awaiter = <getContent>5__2.GetAwaiter();

        DoInDependentWork();
        return content.Length;
    }
    static void DoInDependentWork()
    {

        Console.WriteLine($"content.Length:Working");

    }
}

IL反编译后的代码

【C# TAP 异步编程】三、async\await的运作机理详解
// MyTask.Program
using System;
using System.Diagnostics;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using MyTask;

[System.Runtime.CompilerServices.NullableContext(1)]
[System.Runtime.CompilerServices.Nullable(0)]
internal class Program
{
    [Serializable]
    [CompilerGenerated]
    private sealed class <>c
    {
        [System.Runtime.CompilerServices.Nullable(0)]
        public static readonly <>c <>9 = new <>c();

        [System.Runtime.CompilerServices.Nullable(new byte[] { 0, 1 })]
        public static Action<Task<int>> <>9__0_0;

        internal void <Main>b__0_0(Task<int> t)
        {
            Console.WriteLine(t.Result);
        }
    }

    [CompilerGenerated]
    private sealed class <GetUrlContentLengthAsync>d__1 : IAsyncStateMachine
    {
        public int <>1__state;

        [System.Runtime.CompilerServices.Nullable(0)]
        public AsyncTaskMethodBuilder<int> <>t__builder;

        public int slices;

        [System.Runtime.CompilerServices.Nullable(0)]
        private HttpClient <httpClient>5__1;

        [System.Runtime.CompilerServices.Nullable(new byte[] { 0, 1 })]
        private Task<string> <getContent>5__2;

        [System.Runtime.CompilerServices.Nullable(0)]
        private string <content>5__3;

        [System.Runtime.CompilerServices.Nullable(0)]
        private string <>s__4;

        [System.Runtime.CompilerServices.Nullable(new byte[] { 0, 1 })]
        private TaskAwaiter<string> <>u__1;

        private void MoveNext()
        {
            int num = <>1__state;
            int length;
            try
            {
                TaskAwaiter<string> awaiter;
                if (num != 0)
                {
                    <httpClient>5__1 = new HttpClient();
                    <getContent>5__2 = <httpClient>5__1.GetStringAsync("https://www.cnblogs.com/majiang/p/7899202.html");
                    awaiter = <getContent>5__2.GetAwaiter();
                    if (!awaiter.IsCompleted)
                    {
                        num = (<>1__state = 0);
                        <>u__1 = awaiter;
                        <GetUrlContentLengthAsync>d__1 stateMachine = this;
                        <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
                        return;
                    }
                }
                else
                {
                    awaiter = <>u__1;
                    <>u__1 = default(TaskAwaiter<string>);
                    num = (<>1__state = -1);
                }
                <>s__4 = awaiter.GetResult();
                <content>5__3 = <>s__4;
                <>s__4 = null;
                DoInDependentWork();
                length = <content>5__3.Length;
            }
            catch (Exception exception)
            {
                <>1__state = -2;
                <httpClient>5__1 = null;
                <getContent>5__2 = null;
                <content>5__3 = null;
                <>t__builder.SetException(exception);
                return;
            }
            <>1__state = -2;
            <httpClient>5__1 = null;
            <getContent>5__2 = null;
            <content>5__3 = null;
            <>t__builder.SetResult(length);
        }

        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);
        }
    }

    public static void Main(string[] args)
    {
        Task<int> baconTask = GetUrlContentLengthAsync(3);
        baconTask.ContinueWith(<>c.<>9__0_0 ?? (<>c.<>9__0_0 = new Action<Task<int>>(<>c.<>9.<Main>b__0_0)));
        Console.Read();
    }

    [AsyncStateMachine(typeof(<GetUrlContentLengthAsync>d__1))]
    [DebuggerStepThrough]
    private static Task<int> GetUrlContentLengthAsync(int slices)
    {
        <GetUrlContentLengthAsync>d__1 stateMachine = new <GetUrlContentLengthAsync>d__1();
        stateMachine.<>t__builder = AsyncTaskMethodBuilder<int>.Create();
        stateMachine.slices = slices;
        stateMachine.<>1__state = -1;
        stateMachine.<>t__builder.Start(ref stateMachine);
        return stateMachine.<>t__builder.Task;
    }

    private static void DoInDependentWork()
    {
        Console.WriteLine("content.Length:Working");
    }
}
View Code

 通过IL代码我们可以分析出整个代码运行过程 如下:

【C# TAP 异步编程】三、async\await的运作机理详解

 

关系图中的数字对应于以下步骤,在调用方法调用异步方法时启动。

  1. Main调用方法调用并等待 GetUrlContentLengthAsync 异步方法。

  2. GetUrlContentLengthAsync 可创建 HttpClient 实例并调用 GetStringAsync 异步方法以下载网站内容作为字符串。

  3. GetStringAsync 中发生了某种情况,该情况挂起了它的进程。 可能必须等待网站下载或一些其他阻止活动。 为避免阻止资源,GetStringAsync 会将控制权出让给其调用方 GetUrlContentLengthAsync

    GetStringAsync 返回 Task<TResult>,其中 TResult 为字符串,并且 GetUrlContentLengthAsync 将任务分配给 getStringTask 变量。 该任务表示调用 GetStringAsync 的正在进行的进程,其中承诺当工作完成时产生实际字符串值。

  4. 由于尚未等待 getStringTask,因此,GetUrlContentLengthAsync 可以继续执行不依赖于 GetStringAsync 得出的最终结果的其他工作。 该任务由对同步方法 DoIndependentWork 的调用表示。

  5. DoIndependentWork 是完成其工作并返回其调用方的同步方法。

  6. GetUrlContentLengthAsync 已运行完毕,可以不受 getStringTask 的结果影响。 接下来,GetUrlContentLengthAsync 需要计算并返回已下载的字符串的长度,但该方法只有在获得字符串的情况下才能计算该值。

    因此,GetUrlContentLengthAsync 使用一个 await 运算符来挂起其进度,并把控制权交给调用 GetUrlContentLengthAsync 的方法。 GetUrlContentLengthAsyncTask<int> 返回给调用方。 该任务表示对产生下载字符串长度的整数结果的一个承诺。

    备注

    如果 GetStringAsync(因此 getStringTask)在 GetUrlContentLengthAsync 等待前完成,则控制会保留在 GetUrlContentLengthAsync 中。 如果异步调用过程 getStringTask 已完成,并且 GetUrlContentLengthAsync 不必等待最终结果,则挂起然后返回到 GetUrlContentLengthAsync 将造成成本浪费。

    在调用方法中,处理模式会继续。 在等待结果前,调用方可以开展不依赖于 GetUrlContentLengthAsync 结果的其他工作,否则就需等待片刻。 调用方法等待 GetUrlContentLengthAsync,而 GetUrlContentLengthAsync 等待 GetStringAsync

  7. GetStringAsync 完成并生成一个字符串结果。 字符串结果不是通过按你预期的方式调用 GetStringAsync 所返回的。 (记住,该方法已返回步骤 3 中的一个任务)。相反,字符串结果存储在表示 getStringTask 方法完成的任务中。 await 运算符从 getStringTask 中检索结果。   赋值语句将检索到的结果赋给 contents

  8. GetUrlContentLengthAsync 具有字符串结果时,该方法可以计算字符串长度。 然后,GetUrlContentLengthAsync 工作也将完成,并且等待事件处理程序可继续使用。 在此主题结尾处的完整示例中,可确认事件处理程序检索并打印长度结果的值。 如果你不熟悉异步编程,请花 1 分钟时间考虑同步行为和异步行为之间的差异。 当其工作完成时(第 5 步)会返回一个同步方法,但当其工作挂起时(第 3 步和第 6 步),异步方法会返回一个任务值。 在异步方法最终完成其工作时,任务会标记为已完成,而结果(如果有)将存储在任务中。

 

以上内容来自:https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/async/task-asynchronous-programming-model

 

 

反编译后我们可以看到async\await的运作机理主要分为分异步状态机和等待器,现在我主要来讲解着两部分的运行机制。

 

1、异步状态机

(1)异步状态机 始状态是-1;

(2)运行第一个【等待器】 期间异步状态机的状态是0。

(3)第一个【等待器】完成后异步状态机状态恢复-1。

(4)运行等二个【等待器】期间 异步状态机的状态是1,后面【等待器】以此类推。

(5)当所有的等待器都完成后,异步状态机的状态为-2。

【C# TAP 异步编程】三、async\await的运作机理详解

异步状态机的组成

 

 【C# TAP 异步编程】三、async\await的运作机理详解

 

 

 

 

2、等待器

(1)有返回值的等待器,它返回值就是异步方法public Task<TResult> GetStringAsync(string? requestUri)返回值Task<TResult> 中的TResult。

此例子 中httpClient.GetStringAsync() 返回值是Task<string>。

所以等待器 TaskAwaiter的返回值是 string(Task<string>中的string)。

【C# TAP 异步编程】三、async\await的运作机理详解

3、通过IL代码,查看异步的执行过程,以及最后是如何把任务交给线程池运行

【C# TAP 异步编程】三、async\await的运作机理详解

 异步代码大概运行顺序:

步骤1、主程序main调用异步f方法GetUrlContentLengthAsync()

步骤2、GetUrlContentLengthAsync();完成状态机初始化,然后调用状态机中异步任务方法构建器 中的开始函数:stateMachine.<>t__builder.Start(ref stateMachine);

步骤3、运行Start()函数中的AsyncMethodBuilderCore.Start(ref stateMachine);语句,保存当前线程的ExecutionContext(执行状态机)和SynchronizationContext(同步状态机),然后执行stateMachine.MoveNext();

该语句调用状态机的 MoveNext()方法;步骤4 开始进入状态机的MoveNext()方法;

 

步骤4、生成等待器 awaiter = <getContent>5__2.GetAwaiter(); 将状态机更改成0;然后运行<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);

 步骤5开始, 就是任务是一步一步被送入线程池运行,然后程序把控制权还给调用程序main。

步骤5、运行AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine, ref m_task);

步骤6、生成状态机盒子,将状态机和当前线程执行上下文 封装入状态机 代码如下:

IAsyncStateMachineBox stateMachineBox = GetStateMachineBox(ref stateMachine, ref taskField);

然后运行AwaitUnsafeOnCompleted(ref awaiter, stateMachineBox);

步骤7、执行TaskAwaiter.UnsafeOnCompletedInternal(Unsafe.As<TAwaiter, TaskAwaiter>(ref awaiter).m_task, box, true);
        return;

步骤8、运行OutputWaitEtwEvents(task, stateMachineBox.MoveNextAction,将任务和状态机的moveNext()封装成新的 委托。然后task.SetContinuationForAwait()运行该委托。

捕获当前同步上下文,

步骤9、运行AwaitTaskContinuation.UnsafeScheduleAction(continuationAction, this);

步骤10、ThreadPool.UnsafeQueueUserWorkItemInternal(awaitTaskContinuation, true);这一步将步骤8生成的委托交给线程池。线程池 会分配一个线程来运行异步任务,完成任务后会执行根据这个委托中的moveNext(),因为执行上下文已经一起封装进去了,所以

线程能准确找到moveNext()。 到此为止步骤4的函数执行完毕。程序继续执行 骤4后的return;将控制权交给调用函数main。

当线程完成任务后会继续执行调用状态机的moveNext(),直到所有等待器执行完毕。

参考: https://www.cnblogs.com/lsxqw2004/p/4922374.html

上一篇:Web自动化测试之playwright:概述


下一篇:map函数中调用多个async await请求出现的promise问题解决