c# 理解await和async语法糖
2022/7/11 1:22:38
本文主要是介绍c# 理解await和async语法糖,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
很早以前就想写点东西,奈何总是比较懒,园子里面也有了很多优秀的文章,但是还是决定写点自己的理解,毕竟自己写了才是真的理解。
1.介绍
c# 中我们使用 await和async来进行异步编程,现在我们已经全面拥抱异步,很多同步方法都被废弃了。实际的开发过程中也是用的比较顺畅和舒服的,和同步代码来说没有什么不同,但是很明显这只是微软给我们提供的语法糖,还是需要深入理解其中的奥秘的。
2.从代码开始
Console.WriteLine("the begin"); await TestAsycn(); Console.WriteLine("the end Hello, World!"); static Task TestAsycn() { return Task.Run(() => Console.WriteLine("hello the bug")); }
以上是 .net core 6 代码,很简单,打印三句话,第二行用到了异步,和正常的同步代码来说没什么不同。这样是看不出什么来的,这时就展现.neter开发的精髓了,面向反编译编程。使用dnspy,主要是方便调试(可以使用ILSpy或者dnSpy)。
program.cs
[CompilerGenerated] internal class Program { // Token: 0x06000005 RID: 5 RVA: 0x00002090 File Offset: 0x00000290 private static Task <Main>$(string[] args) { Program.<<Main>$>d__0 <<Main>$>d__; <<Main>$>d__.<>t__builder = AsyncTaskMethodBuilder.Create(); <<Main>$>d__.<>1__state = -1; <<Main>$>d__.<>t__builder.Start<Program.<<Main>$>d__0>(ref <<Main>$>d__); return <<Main>$>d__.<>t__builder.Task; } // Token: 0x06000007 RID: 7 RVA: 0x000020D4 File Offset: 0x000002D4 private static void <Main>(string[] args) { Program.<Main>$(args).GetAwaiter().GetResult(); } // Token: 0x06000008 RID: 8 RVA: 0x000020F4 File Offset: 0x000002F4 [NullableContext(1)] [CompilerGenerated] internal static Task <<Main>$>g__TestAsycn|0_0() { return Task.Run(delegate() { Console.WriteLine("hello the bug"); }); } // Token: 0x02000006 RID: 6 [CompilerGenerated] [StructLayout(LayoutKind.Auto)] private struct <<Main>$>d__0 : IAsyncStateMachine { // Token: 0x06000009 RID: 9 RVA: 0x0000211C File Offset: 0x0000031C void IAsyncStateMachine.MoveNext() { int num = this.<>1__state; try { TaskAwaiter awaiter; if (num != 0) { Console.WriteLine("the begin"); //调用并获取任务的awaiter awaiter = Program.<<Main>$>g__TestAsycn|0_0().GetAwaiter(); if (!awaiter.IsCompleted) { this.<>1__state = 0; this.<>u__1 = awaiter; //注册任务回调 this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, Program.<<Main>$>d__0>(ref awaiter, ref this); //直接返回不会阻塞 return; } } else { awaiter = this.<>u__1; this.<>u__1 = default(TaskAwaiter); this.<>1__state = -1; } awaiter.GetResult(); Console.WriteLine("the end Hello, World!"); } catch (Exception ex) { this.<>1__state = -2; this.<>t__builder.SetException(ex); return; } this.<>1__state = -2; this.<>t__builder.SetResult(); } // Token: 0x0600000A RID: 10 RVA: 0x000021DC File Offset: 0x000003DC [DebuggerHidden] void IAsyncStateMachine.SetStateMachine([Nullable(1)] IAsyncStateMachine stateMachine) { this.<>t__builder.SetStateMachine(stateMachine); } // Token: 0x04000003 RID: 3 public int <>1__state; // Token: 0x04000004 RID: 4 public AsyncTaskMethodBuilder <>t__builder; // Token: 0x04000005 RID: 5 private TaskAwaiter <>u__1; } }
可以看到两个mian方法,一个同步一个异步,这就是编译器背后做的事情,同步的main方法调用异步的main方法。具体来看看异步的代码。
1.首先声明一个 继承了 IAsyncStateMachine的状态机 <
2.调用builder的start方法,builder类型是 AsyncTaskMethodBuilder,点进去看看 builder是个什么东西。
AsyncTaskMethodBuilder
public struct AsyncTaskMethodBuilder<[Nullable(2)] TResult> { [DebuggerStepThrough] [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Start<[Nullable(0)] TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine { // 进去看看=>函数2 AsyncMethodBuilderCore.Start<TStateMachine>(ref stateMachine); } } //函数2 internal static class AsyncMethodBuilderCore { public static void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine { if (stateMachine == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine); } Thread currentThread = Thread.CurrentThread; ExecutionContext executionContext = currentThread._executionContext; SynchronizationContext synchronizationContext = currentThread._synchronizationContext; try { //状态机调用 stateMachine.MoveNext(); } finally { if (synchronizationContext != currentThread._synchronizationContext) { currentThread._synchronizationContext = synchronizationContext; } ExecutionContext executionContext2 = currentThread._executionContext; if (executionContext != executionContext2) { ExecutionContext.RestoreChangedContextToThread(currentThread, executionContext, executionContext2); } } } }
可以看出实际上是调用了状态机的movenext方法,好的继续看。代码已经在最上面贴出来了。
movenext
梳理一下逻辑:
初始状态 num = -1 , 控制台打印 ,调用异步函数,获取awaiter ,所有可等待的对象必定可以获取awaiter,,可以查看此任务是否完成,未完成:则会调用 AwaitUnsafeOnCompleted 函数 实际上这个函数就是注册任务完成的回调函数。
AwaitUnsafeOnCompleted 等一系列方法
public struct AsyncTaskMethodBuilder<[Nullable(2)] TResult> { // 函数1 public void AwaitOnCompleted<[Nullable(0)] TAwaiter, [Nullable(0)] TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine { // 调用下面的函数 AsyncTaskMethodBuilder<VoidTaskResult>.AwaitOnCompleted<TAwaiter, TStateMachine>(ref awaiter, ref stateMachine, ref this.m_task); } // 函数2 [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AwaitUnsafeOnCompleted<[Nullable(0)] TAwaiter, [Nullable(0)] TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine { AsyncTaskMethodBuilder<VoidTaskResult>.AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref awaiter, ref stateMachine, ref this.m_task); } // 函数3 internal static void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine, [NotNull] ref Task<TResult> taskField) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine { IAsyncStateMachineBox stateMachineBox = AsyncTaskMethodBuilder<TResult>.GetStateMachineBox<TStateMachine>(ref stateMachine, ref taskField); AsyncTaskMethodBuilder<TResult>.AwaitUnsafeOnCompleted<TAwaiter>(ref awaiter, stateMachineBox); } }
函数1 => 函数2 => 函数 3,分别调用。
可以看到函数 3 对我们的状态机进行了包装,生成一个状态机盒子,进去看看。
GetStateMachineBox
private static IAsyncStateMachineBox GetStateMachineBox<TStateMachine>(ref TStateMachine stateMachine, [NotNull] ref Task<TResult> taskField) where TStateMachine : IAsyncStateMachine { //捕捉当前环境上下文 ExecutionContext executionContext = ExecutionContext.Capture(); //根据调试的内容taskfield 为null 如果有多个await代码段 第二次movenext就会走这段逻辑 AsyncTaskMethodBuilder<TResult>.AsyncStateMachineBox<TStateMachine> asyncStateMachineBox = taskField as AsyncTaskMethodBuilder<TResult>.AsyncStateMachineBox<TStateMachine>; if (asyncStateMachineBox != null) { if (asyncStateMachineBox.Context != executionContext) { asyncStateMachineBox.Context = executionContext; } return asyncStateMachineBox; } AsyncTaskMethodBuilder<TResult>.AsyncStateMachineBox<IAsyncStateMachine> asyncStateMachineBox2 = taskField as AsyncTaskMethodBuilder<TResult>.AsyncStateMachineBox<IAsyncStateMachine>; if (asyncStateMachineBox2 != null) { if (asyncStateMachineBox2.StateMachine == null) { Debugger.NotifyOfCrossThreadDependency(); asyncStateMachineBox2.StateMachine = stateMachine; } asyncStateMachineBox2.Context = executionContext; return asyncStateMachineBox2; } Debugger.NotifyOfCrossThreadDependency(); //走以下逻辑 AsyncTaskMethodBuilder<TResult>.AsyncStateMachineBox<TStateMachine> asyncStateMachineBox3 = (AsyncMethodBuilderCore.TrackAsyncMethodCompletion ? AsyncTaskMethodBuilder<TResult>.CreateDebugFinalizableAsyncStateMachineBox<TStateMachine>() : new AsyncTaskMethodBuilder<TResult>.AsyncStateMachineBox<TStateMachine>()); taskField = asyncStateMachineBox3; asyncStateMachineBox3.StateMachine = stateMachine; asyncStateMachineBox3.Context = executionContext; if (TplEventSource.Log.IsEnabled()) { TplEventSource.Log.TraceOperationBegin(asyncStateMachineBox3.Id, "Async: " + stateMachine.GetType().Name, 0L); } if (System.Threading.Tasks.Task.s_asyncDebuggingEnabled) { System.Threading.Tasks.Task.AddToActiveTasks(asyncStateMachineBox3); } return asyncStateMachineBox3; }
看到了 ExecutionContext 类,一个很特殊的类,用于盛放其他上下文的容器,这里的作用捕捉当前线程的上下文,当线程切换的时候为线程提供运行环境,方便以后继续调用。
继续看AwaitUnsafeOnCompleted 方法:
AwaitUnsafeOnCompleted
internal static void AwaitUnsafeOnCompleted<TAwaiter>(ref TAwaiter awaiter, IAsyncStateMachineBox box) where TAwaiter : ICriticalNotifyCompletion { //走以下逻辑 if (default(TAwaiter) != null && awaiter is ITaskAwaiter) { ref TaskAwaiter ptr = ref Unsafe.As<TAwaiter, TaskAwaiter>(ref awaiter); //走此逻辑 TaskAwaiter.UnsafeOnCompletedInternal(ptr.m_task, box, true); return; } //以下代码不走被我删除 ....... } //往下走 internal static void UnsafeOnCompletedInternal(Task task, IAsyncStateMachineBox stateMachineBox, bool continueOnCapturedContext) { if (TplEventSource.Log.IsEnabled() || Task.s_asyncDebuggingEnabled) { task.SetContinuationForAwait(TaskAwaiter.OutputWaitEtwEvents(task, stateMachineBox.MoveNextAction), continueOnCapturedContext, false); return; } //走此逻辑 task.UnsafeSetContinuationForAwait(stateMachineBox, continueOnCapturedContext); } //往下走 internal void UnsafeSetContinuationForAwait(IAsyncStateMachineBox stateMachineBox, bool continueOnCapturedContext) { //true if (continueOnCapturedContext) { //synchronizationContext=null 跳过 SynchronizationContext synchronizationContext = SynchronizationContext.Current; if (synchronizationContext != null && synchronizationContext.GetType() != typeof(SynchronizationContext)) { SynchronizationContextAwaitTaskContinuation synchronizationContextAwaitTaskContinuation = new SynchronizationContextAwaitTaskContinuation(synchronizationContext, stateMachineBox.MoveNextAction, false); if (!this.AddTaskContinuation(synchronizationContextAwaitTaskContinuation, false)) { synchronizationContextAwaitTaskContinuation.Run(this, false); } return; } // internalCurrent =null 跳过 TaskScheduler internalCurrent = TaskScheduler.InternalCurrent; if (internalCurrent != null && internalCurrent != TaskScheduler.Default) { TaskSchedulerAwaitTaskContinuation taskSchedulerAwaitTaskContinuation = new TaskSchedulerAwaitTaskContinuation(internalCurrent, stateMachineBox.MoveNextAction, false); if (!this.AddTaskContinuation(taskSchedulerAwaitTaskContinuation, false)) { taskSchedulerAwaitTaskContinuation.Run(this, false); } return; } } //查看异步任务是否完成,完成返回true,直接将后续任务交给线程池处理,否则注册回调任务 if (!this.AddTaskContinuation(stateMachineBox, false)) { ThreadPool.UnsafeQueueUserWorkItemInternal(stateMachineBox, true); } } //往下走 private bool AddTaskContinuation(object tc, bool addBeforeOthers) { return !this.IsCompleted && ((this.m_continuationObject == null && Interlocked.CompareExchange(ref this.m_continuationObject, tc, null) == null) || this.AddTaskContinuationComplex(tc, addBeforeOthers)); } //最后将任务注册进入 m_continuationObject,这是存储回调的字段,task完成后会执行。 Interlocked.CompareExchange(ref this.m_continuationObject, tc, null)
m_continuationObject 什么时候调用呢 ,task完成的时候会调用。
task相关
internal void FinishContinuations() { object obj = Interlocked.Exchange(ref this.m_continuationObject, Task.s_taskCompletionSentinel); if (obj != null) { this.RunContinuations(obj); } }
task 完成后 会调用 回调。
所以整个流程走了一遍基本清晰了,明白了await 和async背后的东西,以后我们在使用的时候也会更加得心应手。
总结
个人理解 await 和 async 背后实际上是状态机,将await 后面的代码注册为了 task完成后的回调任务,这样保证了先后顺序。其实写这篇文章前 最大的疑惑是 为什么注册的回调能在task之后运行,没看到相应的逻辑代码,只是凭借自己的理解 代码可能会走 ThreadPool.UnsafeQueueUserWorkItemInternal(stateMachineBox, true); 这部分代码,就是把回调丢到线程池里去完成,这就让我很懵,这怎么能保证先后顺序呢,于是自己调试走了一遍流程才发现 原来不走这个逻辑,即使走这个逻辑, 那么task任务也是完成了的,始终保持逻辑的先后顺序。所以还是需要实践,想当然是行不通的。
附:不妨看看下面的文章
https://stackoverflow.com/questions/55811416/why-does-continuation-start-before-the-getresult
ExecutionContext 综述
task await async 解析
走进task 任务回调与执行
这篇关于c# 理解await和async语法糖的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2022-03-01沐雪多租宝商城源码从.NetCore3.1升级到.Net6的步骤
- 2024-03-30C#设计模式之十六迭代器模式(Iterator Pattern)【行为型】
- 2024-03-29c# datetime tryparse
- 2024-02-21list find index c#
- 2024-01-24convert toint32 c#
- 2024-01-24Advanced .Net Debugging 1:你必须知道的调试工具
- 2024-01-24.NET集成IdGenerator生成分布式全局唯一ID
- 2024-01-23用CI/CD工具Vela部署Elasticsearch + C# 如何使用
- 2024-01-23.NET开源的简单、快速、强大的前后端分离后台权限管理系统
- 2024-01-23C#对象二进制序列化优化:位域技术实现极限压缩