前言

一个老掉牙的话题,园子里的相关优秀文章已经有很多了,我写这篇文章完全是想以自己的思维方式来谈一谈自己的理解。(PS:文中涉及到了大量反编译源码,需要静下心来细细品味)

从简单开始

为了更容易理解这个问题,我们举一个简单的例子:用异步的方式在控制台上分两步输出“Hello World!”,我这边使用的是Framework 4.5.2

class Program
{
    static async Task Main(string[] args)
    {
        Console.WriteLine("Let's Go!");
    
        await TestAsync();
        
        Console.Write(" World!");
    }

    static Task TestAsync()
    {
        return Task.Run(() =>
        {
            Console.Write("Hello");
        });
    }
} 

探究反编译后的源码

接下来我们使用 .NET reflector (也可使用 dnSpy 等) 反编译一下程序集,然后一步一步来探究 async await 内部的奥秘。

Main方法

[DebuggerStepThrough]
private static void 
(string[] args) { Main(args).GetAwaiter().GetResult(); } [AsyncStateMachine(typeof(
d__0)), DebuggerStepThrough] private static Task Main(string[] args) {
d__0 stateMachine = new
d__0 { <>t__builder = AsyncTaskMethodBuilder.Create(), args = args, <>1__state = -1 }; stateMachine.<>t__builder.Start<
d__0>(ref stateMachine); return stateMachine.<>t__builder.Task; } // 实现了 IAsyncStateMachine 接口 [CompilerGenerated] private sealed class
d__0 : IAsyncStateMachine { // Fields public int <>1__state; public AsyncTaskMethodBuilder <>t__builder; public string[] args; private TaskAwaiter <>u__1; // Methods private void MoveNext() { } [DebuggerHidden] private void SetStateMachine(IAsyncStateMachine stateMachine) { } }

卧槽!竟然有两个 Main 方法:一个同步、一个异步。原来,虽然我们写代码时为了在 Main 方法中方便异步等待,将 void Main 改写成了async Task Main,但是实际上程序入口仍是我们熟悉的那个 void Main。

另外,我们可以看到异步 Main 方法被标注了AsyncStateMachine特性,这是因为在我们的源代码中,该方法带有修饰符async,表示该方法是一个异步方法。

好,我们先看一下异步Main方法内部实现,它主要做了三件事:

  1. 首先,创建了一个类型为
    d__0的状态机 stateMachine,并初始化了公共变量 <>t__builder、args、<>1__state = -1
    • <>t__builder:负责异步相关的操作,是实现异步 Main 方法异步的核心
    • <>1__state:状态机的当前状态
  2. 然后,调用Start方法,借助 stateMachine, 来执行我们在异步 Main 方法中写的代码
  3. 最后,将指示异步 Main 方法运行状态的Task对象返回出去

Start

首先,我们先来看一下Start的内部实现

// 所属结构体:AsyncTaskMethodBuilder

[SecuritySafeCritical, DebuggerStepThrough, __DynamicallyInvokable]
public void Start(ref TStateMachine stateMachine) where TStateMachine: IAsyncStateMachine
{
    if (((TStateMachine) stateMachine) == null)
    {
        throw new ArgumentNullException("stateMachine");
    }
    ExecutionContextSwitcher ecsw = new ExecutionContextSwitcher();
    RuntimeHelpers.PrepareConstrainedRegions();
    try
    {
        ExecutionContext.EstablishCopyOnWriteScope(ref ecsw);
        // 状态机状态流转
        stateMachine.MoveNext();
    }
    finally
    {
        ecsw.Undo();
    }
} 

我猜,你只能看懂stateMachine.MoveNext(),对不对?好,那我们就来看看这个状态机类

d__0,并且着重看它的方法MoveNext


MoveNext

[CompilerGenerated]
private sealed class 
d__0 : IAsyncStateMachine { // Fields public int <>1__state; public AsyncTaskMethodBuilder <>t__builder; public string[] args; private TaskAwaiter <>u__1; // Methods private void MoveNext() { // 在 Main 方法中,我们初始化 <>1__state = -1,所以此时 num = -1 int num = this.<>1__state; try { TaskAwaiter awaiter; if (num != 0) { Console.WriteLine("Let's Go!"); // 调用 TestAsync(),获取 awaiter,用于后续监控 TestAsync() 运行状态 awaiter = Program.TestAsync().GetAwaiter(); // 一般来说,异步任务不会很快就完成,所以大多数情况下都会进入该分支 if (!awaiter.IsCompleted) { // 状态机状态从 -1 流转为 0 this.<>1__state = num = 0; this.<>u__1 = awaiter; Program.
d__0 stateMachine = this; // 配置 TestAsync() 完成后的延续 this.<>t__builder.AwaitUnsafeOnCompletedd__0>(ref awaiter, ref stateMachine); return; } } else { awaiter = this.<>u__1; this.<>u__1 = new TaskAwaiter(); this.<>1__state = num = -1; } awaiter.GetResult(); Console.Write(" World!"); } catch (Exception exception) { this.<>1__state = -2; this.<>t__builder.SetException(exception); return; } this.<>1__state = -2; this.<>t__builder.SetResult(); } [DebuggerHidden] private void SetStateMachine(IAsyncStateMachine stateMachine) { } }

先简单理一下内部逻辑:

  1. 设置变量 num = -1,此时 num != 0,则会进入第一个if语句,
  2. 首先,执行Console.WriteLine("Let's Go!")
  3. 然后,调用异步方法TestAsyncTestAsync方法会在另一个线程池线程中执行,并获取指示该方法运行状态的 awaiter
  4. 如果此时TestAsync方法已执行完毕,则像没有异步一般:
    1. 继续执行接下来的Console.Write(" World!")
    2. 最后设置 <>1__state = -2,并设置异步 Main 方法的返回结果
  5. 如果此时TestAsync方法未执行完毕,则:
    1. 设置 <>1__state = num = 0
    2. 调用AwaitUnsafeOnCompleted方法,用于配置当TestAsync方法完成时的延续,即Console.Write(" World!")
    3. 返回指示异步 Main 方法执行状态的 Task 对象,由于同步 Main 方法中通过使用GetResult()同步阻塞主线程等待任务结束,所以不会释放主线程(废话,如果释放了程序就退出了)。不过对于其他子线程,一般会释放该线程

大部分逻辑我们都可以很容易的理解,唯一需要深入研究的就是AwaitUnsafeOnCompleted,那我们接下来就看看它的内部实现

AwaitUnsafeOnCompleted

// 所属结构体:AsyncTaskMethodBuilder

[__DynamicallyInvokable]
public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter: ICriticalNotifyCompletion where TStateMachine: IAsyncStateMachine
{
    this.m_builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
}

// 所属结构体:AsyncTaskMethodBuilder

[SecuritySafeCritical, __DynamicallyInvokable]
public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter: ICriticalNotifyCompletion where TStateMachine: IAsyncStateMachine
{
    try
    {
        // 用于流转状态机状态的 runner
        AsyncMethodBuilderCore.MoveNextRunner runnerToInitialize = null;
        Action completionAction = this.m_coreState.GetCompletionAction(AsyncCausalityTracer.LoggingOn ? this.Task : null, ref runnerToInitialize);
        if (this.m_coreState.m_stateMachine == null)
        {
            // 此处构建指示异步 Main 方法执行状态的 Task 对象
            Task builtTask = this.Task;
            this.m_coreState.PostBoxInitialization((TStateMachine) stateMachine, runnerToInitialize, builtTask);
        }
        awaiter.UnsafeOnCompleted(completionAction);
    }
    catch (Exception exception)
    {
        AsyncMethodBuilderCore.ThrowAsync(exception, null);
    }
} 

咱们一步一步来,先看一下GetCompletionAction的实现:

// 所属结构体:AsyncMethodBuilderCore

[SecuritySafeCritical]
internal Action GetCompletionAction(Task taskForTracing, ref MoveNextRunner runnerToInitialize)
{
    Action defaultContextAction;
    MoveNextRunner runner;
    Debugger.NotifyOfCrossThreadDependency();
    // 
    ExecutionContext context = ExecutionContext.FastCapture();
    if ((context != null) && context.IsPreAllocatedDefault)
    {
        defaultContextAction = this.m_defaultContextAction;
        if (defaultContextAction != null)
        {
            return defaultContextAction;
        }
        
        // 构建 runner
        runner = new MoveNextRunner(context, this.m_stateMachine);
        // 返回值
        defaultContextAction = new Action(runner.Run);
        if (taskForTracing != null)
        {
            this.m_defaultContextAction = defaultContextAction = this.OutputAsyncCausalityEvents(taskForTracing, defaultContextAction);
        }
        else
        {
            this.m_defaultContextAction = defaultContextAction;
        }
    }
    else
    {
        runner = new MoveNextRunner(context, this.m_stateMachine);
        defaultContextAction = new Action(runner.Run);
        if (taskForTracing != null)
        {
            defaultContextAction = this.OutputAsyncCausalityEvents(taskForTracing, defaultContextAction);
        }
    }
    if (this.m_stateMachine == null)
    {
        runnerToInitialize = runner;
    }
    return defaultContextAction;
} 

发现一个熟悉的家伙——ExecutionContext,它是用来给咱们延续方法(即Console.Write(" World!");)提供运行环境的,注意这里用的是FastCapture(),该内部方法并未捕获SynchronizationContext,因为不需要流动它。什么?你说你不认识它?大眼瞪小眼?那你应该好好看看《理解C#中的ExecutionContext vs SynchronizationContext》了

接着来到new MoveNextRunner(context, this.m_stateMachine),这里初始化了 runner,我们看看构造函数中做了什么:

[SecurityCritical]
internal MoveNextRunner(ExecutionContext context, IAsyncStateMachine stateMachine)
{
    // 将 ExecutionContext 保存了下来
    this.m_context = context;
    
    // 将 stateMachine 保存了下来(不过此时为 null)
    this.m_stateMachine = stateMachine;
} 

往下来到defaultContextAction = new Action(runner.Run),你可以发现,最终咱们返回的就是这个 defaultContextAction ,所以这个runner.Run至关重要,不过别着急,我们等用到它的时候我们再来看其内部实现。

最后,回到AwaitUnsafeOnCompleted方法,继续往下走。构建指示异步 Main 方法执行状态的 Task 对象,设置当前的状态机后,来到awaiter.UnsafeOnCompleted(completionAction);,要记住,入参 completionAction 就是刚才返回的runner.Run

// 所属结构体:TaskAwaiter

[SecurityCritical, __DynamicallyInvokable]
public void UnsafeOnCompleted(Action continuation)
{
    OnCompletedInternal(this.m_task, continuation, true, false);
}

[MethodImpl(MethodImplOptions.NoInlining), SecurityCritical]
internal static void OnCompletedInternal(Task task, Action continuation, bool continueOnCapturedContext, bool flowExecutionContext)
{
    if (continuation == null)
    {
        throw new ArgumentNullException("continuation");
    }
    StackCrawlMark lookForMyCaller = StackCrawlMark.LookForMyCaller;
    if (TplEtwProvider.Log.IsEnabled() || Task.s_asyncDebuggingEnabled)
    {
        continuation = OutputWaitEtwEvents(task, continuation);
    }
    
    // 配置延续方法
    task.SetContinuationForAwait(continuation, continueOnCapturedContext, flowExecutionContext, ref lookForMyCaller);
} 

直接来到代码最后一行,看到延续方法的配置

// 所属类:Task

[SecurityCritical]
internal void SetContinuationForAwait(Action continuationAction, bool continueOnCapturedContext, bool flowExecutionContext, ref StackCrawlMark stackMark)
{
    TaskContinuation tc = null;
    if (continueOnCapturedContext)
    {
        // 这里我们用的是不进行流动的 SynchronizationContext
        SynchronizationContext currentNoFlow = SynchronizationContext.CurrentNoFlow;
        // 像 Winform、WPF 这种框架,实现了自定义的 SynchronizationContext,
        // 所以在 Winform、WPF 的 UI线程中进行异步等待时,一般 currentNoFlow 不会为 null
        if ((currentNoFlow != null) && (currentNoFlow.GetType() != typeof(SynchronizationContext)))
        {
            // 如果有 currentNoFlow,那么我就用它来执行延续方法
            tc = new SynchronizationContextAwaitTaskContinuation(currentNoFlow, continuationAction, flowExecutionContext, ref stackMark);
        }
        else
        {
            TaskScheduler internalCurrent = TaskScheduler.InternalCurrent;
            if ((internalCurrent != null) && (internalCurrent != TaskScheduler.Default))
            {
                tc = new TaskSchedulerAwaitTaskContinuation(internalCurrent, continuationAction, flowExecutionContext, ref stackMark);
            }
        }
    }
    if ((tc == null) & flowExecutionContext)
    {
        tc = new AwaitTaskContinuation(continuationAction, true, ref stackMark);
    }
    if (tc != null)
    {
        if (!this.AddTaskContinuation(tc, false))
        {
            tc.Run(this, false);
        }
    }
    // 这里会将 continuationAction 设置为 awaiter 中 task 对象的延续方法,所以当 TestAsync() 完成时,就会执行 runner.Run
    else if (!this.AddTaskContinuation(continuationAction, false))
    {
        AwaitTaskContinuation.UnsafeScheduleAction(continuationAction, this);
    }
} 

对于我们的示例来说,既没有自定义 SynchronizationContext,也没有自定义 TaskScheduler,所以会直接来到最后一个else if (...),重点在于this.AddTaskContinuation(continuationAction, false),这个方法会将我们的延续方法添加到 Task 中,以便于当 TestAsync 方法执行完毕时,执行 runner.Run

runner.Run

好,是时候让我们看看 runner.Run 的内部实现了:

[SecuritySafeCritical]
internal void Run()
{
    if (this.m_context != null)
    {
        try
        {
            // 我们并未给 s_invokeMoveNext 赋值,所以 callback == null
            ContextCallback callback = s_invokeMoveNext;
            if (callback == null)
            {
                // 将回调设置为下方的 InvokeMoveNext 方法
                s_invokeMoveNext = callback = new
                ContextCallback(AsyncMethodBuilderCore.MoveNextRunner.InvokeMoveNext);
            }
            ExecutionContext.Run(this.m_context, callback, this.m_stateMachine, true);
            return;
        }
        finally
        {
            this.m_context.Dispose();
        }
    }
    this.m_stateMachine.MoveNext();
}

[SecurityCritical]
private static void InvokeMoveNext(object stateMachine)
{
    ((IAsyncStateMachine) stateMachine).MoveNext();
} 

来到ExecutionContext.Run(this.m_context, callback, this.m_stateMachine, true);,这里的 callback 是InvokeMoveNext方法。所以,当TestAsync执行完毕后,就会执行延续方法 runner.Run,也就会执行stateMachine.MoveNext()促使状态机继续进行状态流转,这样逻辑就打通了:

private void MoveNext()
{
    // num = 0
    int num = this.<>1__state;
    try
    {
        TaskAwaiter awaiter;
        if (num != 0)
        {
            Console.WriteLine("Let's Go!");
            awaiter = Program.TestAsync().GetAwaiter();

            if (!awaiter.IsCompleted)
            {
                this.<>1__state = num = 0;
                this.<>u__1 = awaiter;
                Program.
d__0 stateMachine = this; this.<>t__builder.AwaitUnsafeOnCompletedd__0>(ref awaiter, ref stateMachine); return; } } else { awaiter = this.<>u__1; this.<>u__1 = new TaskAwaiter(); // 状态机状态从 0 流转到 -1 this.<>1__state = num = -1; } // 结束对 TestAsync() 的等待 awaiter.GetResult(); // 执行延续方法 Console.Write(" World!"); } catch (Exception exception) { this.<>1__state = -2; this.<>t__builder.SetException(exception); return; } // 状态机状态从 -1 流转到 -2 this.<>1__state = -2; // 设置异步 Main 方法最终返回结果 this.<>t__builder.SetResult(); }

至此,整个异步方法的执行就结束了,通过一张图总结一下:

最后,我们看一下各个线程的状态,看看和你的推理是否一致(如果有不清楚的联系我,我会通过文字补充):

多个 async await 嵌套

理解了async await的简单使用,那你可曾想过,如果有多个 async await 嵌套,那会出现什么情况呢?接下来就改造一下我们的例子,来研究研究:

static Task TestAsync()
{
    return Task.Run(async () =>
    {
        // 增加了这行
        await Task.Run(() =>
        {
            Console.Write("Say: ");
        });

        Console.Write("Hello");
    });
} 

反编译之后的代码,上面已经讲解的我就不再重复贴了,主要看看TestAsync()就行了:

private static Task TestAsync() => 
    Task.Run(delegate {
        <>c.<b__1_0>d stateMachine = new <>c.<b__1_0>d {
            <>t__builder = AsyncTaskMethodBuilder.Create(),
            <>4__this = this,
            <>1__state = -1
        };
        stateMachine.<>t__builder.Start<<>c.<b__1_0>d>(ref stateMachine);
        return stateMachine.<>t__builder.Task;
    }); 

哦!原来,async await 的嵌套也就是状态机的嵌套,相信你通过上面的状态机状态流转,也能够梳理除真正的执行逻辑,那我们就只看一下线程状态吧:

这也印证了我上面所说的:当子线程完成执行任务时,会被释放,或回到线程池供其他线程使用。

多个 async await 在同一方法中顺序执行

又可曾想过,如果有多个 async await 在同一方法中顺序执行,又会是何种景象呢?同样,先来个例子:

static async Task Main(string[] args)
{
    Console.WriteLine("Let's Go!");

    await Test1Async();

    await Test2Async();

    Console.Write(" World!");
}

static Task Test1Async()
{
    return Task.Run(() =>
    {
        Console.Write("Say: ");
    });
}

static Task Test2Async()
{
    return Task.Run(() =>
    {
        Console.Write("Hello");
    });
} 

直接看状态机:

[CompilerGenerated]
private sealed class 
d__0 : IAsyncStateMachine { // Fields public int <>1__state; public AsyncTaskMethodBuilder <>t__builder; public string[] args; private TaskAwaiter <>u__1; // Methods private void MoveNext() { int num = this.<>1__state; try { TaskAwaiter awaiter; TaskAwaiter awaiter2; if (num != 0) { if (num == 1) { awaiter = this.<>u__1; this.<>u__1 = default(TaskAwaiter); this.<>1__state = -1; goto IL_D8; } Console.WriteLine("Let's Go!"); awaiter2 = Program.Test1Async().GetAwaiter(); if (!awaiter2.IsCompleted) { this.<>1__state = 0; this.<>u__1 = awaiter2; Program.
d__0
d__ = this; this.<>t__builder.AwaitUnsafeOnCompletedd__0>(ref awaiter2, ref
d__); return; } } else { awaiter2 = this.<>u__1; this.<>u__1 = default(TaskAwaiter); this.<>1__state = -1; } awaiter2.GetResult(); // 待 Test1Async 完成后,继续执行 Test2Async awaiter = Program.Test2Async().GetAwaiter(); if (!awaiter.IsCompleted) { this.<>1__state = 1; this.<>u__1 = awaiter; Program.
d__0
d__ = this; this.<>t__builder.AwaitUnsafeOnCompletedd__0>(ref awaiter, ref
d__); return; } IL_D8: awaiter.GetResult(); Console.Write(" World!"); } catch (Exception exception) { this.<>1__state = -2; this.<>t__builder.SetException(exception); return; } this.<>1__state = -2; this.<>t__builder.SetResult(); } [DebuggerHidden] private void SetStateMachine(IAsyncStateMachine stateMachine) { } }

可见,就是一个状态机状态一直流转就完事了。我们就看看线程状态吧:

WPF中使用 async await

上面我们都是通过控制台举的例子,这是没有任何SynchronizationContext的,但是WPF(Winform同理)就不同了,在UI线程中,它拥有属于自己的DispatcherSynchronizationContext

private async void Button_Click(object sender, RoutedEventArgs e)
{
    // UI 线程会一直保持 Running 状态,不会导致 UI 假死
    Show(Thread.CurrentThread);

    await TestAsync();

    Show(Thread.CurrentThread);
}

private Task TestAsync()
{
    return Task.Run(() =>
    {
        Show(Thread.CurrentThread);
    });
}

private static void Show(Thread thread)
{
    MessageBox.Show($"{nameof(thread.ManagedThreadId)}: {thread.ManagedThreadId}" +
        $"\n{nameof(thread.ThreadState)}: {thread.ThreadState}");
} 

通过使用DispatcherSynchronizationContext执行延续方法,又回到了 UI 线程中

标签:async await

理解C#中的 async await的更多相关文章

  1. C#微信公众号推送消息接口消息排重

    用户在微信公众号发送文本,语音,图片等的普通消息时,微信服务器会向公众号配置的接收消息的地址转发用户消息,微信服务器......

  2. 使用 C# 9 的records作为强类型ID - JSON序列化

    在本系列的上一篇文章中,我们注意到强类型ID的实体,序列化为 JSON 的时候报错了,就像这样:{"id&......

  3. Winform 窗体自适应

    前言在使用 Winform 开发过程中,经常发些因为显示器分辨率、窗体大小改变,控件却不能自适应变化,几经查找资料,......

  4. C# 实现一个基于值相等性比较的字典

    C# 实现一个基于值相等性比较的字典C# 实现一个基于值相等性比较的字典Intro今天在项目里遇到一个需求,大概是这......

  5. 计算机网络安全 —— C# 使用谷歌身份验证器(Google Authenticator)

    一、Google Authenticator 基本概念Google Authenticator是谷歌推出的一款动态口......

  6. C# 中的动态类型

    翻译自 Camilo Reyes 2018年10月15日的文章 《Working with the Dynamic ......

  7. C# 两个类的实例之间相同属性的值的复制

    在进行实体转换操作的时候通常需要在对两个实体之间两个属性字段相同的类要进行一个互相的转换,我们要把a对象的所有字段的......

  8. c#定时执行程序代码

    在一般的项目中我们很少用到c#实现每隔规定时间自动执行程序代码,但是如果你经历的项目多,或者应用程序做的比较多的话,......

  9. 详解如何在C#中使用投影(Projection)

    投影(Projection) 是一种可以将查询结果进行 塑性 的一种操作,你可以使用 投影 将一个 object 转......

  10. 说说C# 8.0 新增功能Index和Range的^0是什么?

    前言在《C# 8.0 中使用 Index 和 Range》这篇中有人提出^0是什么意思?处于好奇就去试了,结果抛出异......

随机推荐

  1. iOS中几种定时器的实现小结

    在软件开发过程中,我们常常需要在某个时间后执行某个方法,或者是按照某个周期一直执行某个方法。在这个时候,我们就需要用......

  2. JavaScript canvas实现文字时钟

    本文实例为大家分享了canvas实现文字时钟的具体代码,供大家参考,具体内容如下 先看看效果图 代码 &l......

  3. 用python制作个音乐下载器

    前言某个夜深人静的夜晚,我打开了自己的文件夹,发现了自己写了许多似乎很无聊的代码。于是乎,一个想法油然而生:“生活已......

  4. 基于Python的接口自动化-读写excel文件

    引言使用python进行接口测试时常常需要接口用例测试数据、断言接口功能、验证接口响应状态等,如果大量的接口测试用例......

  5. MySQL下载地址与Centos7安装MySQL以及启动问题排查

    MySQL国内镜像下载地址以及开源镜像相关站点Centos7安装MySQL启动问题(The server quit ......

  6. 基于Python的接口自动化-读写配置文件

    引言在编写接口自动化测试脚本时,有时我们需要在代码中定义变量并给变量固定的赋值。为了统一管理和操作这些固定的变量,咱......

  7. JavaScript实现浏览器网页自动滚动并点击的示例代码

    1. 打开浏览器控制台窗口JavaScript通常是作为开发Web页面的脚本语言,本文介绍的JavaScript代码......

  8. python用分数表示矩阵的方法实例

    前言在机器学习中,我们会经常和矩阵打交道。在矩阵的运算中,python默认的输出是浮点数,但是如果我们想要矩阵的元素......

  9. java中gc算法实例用法

    在我们对gc中的算法有基本概念理解后,要把算法的理念实现还需要依托实际垃圾收集器的使用。因为光靠一些简单的原理不足以......

  10. nodejs中的文件系统

    、目录简介nodejs中的文件系统模块Promise版本的fs文件描述符fs.stat文件状态信息fs的文件读写fs......