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的状态机 <

$>d__ ,所以此时就可以明白了它们的奥秘就是状态机,然后做了初始化,<>t__builder:负责异步相关的操作,<>1__state:状态机状态

​ 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能够让我们像写同步代码一样写异步代码,如果一个方法是异步的,也就是说用async标记了,如果我们调用的时候,不使用await去等待,那么程序就会继续往下走,不会等待方法挖成,如果我们使用await等待方法,才会去等待。await async也是语法糖,反编译看到了背后实际上是状态机,如果说看源码的话,就是查看调用方法的GetAwaiter,查看此task是否完成,完成了话皆大欢喜继续执行下面的代码,如果没完成就执行AwaitUnsafeOnCompleted,然后直接返回了,而AwaitUnsafeOnCompleted实际上是将task后面的代码注册为了 task完成后的回调任务,任务完成后会接着执行回调。这样保证了先后顺序。其实写这篇文章前 最大的疑惑是 为什么注册的回调能在task之后运行,没看到相应的逻辑代码,只是凭借自己的理解 代码可能会走 ThreadPool.UnsafeQueueUserWorkItemInternal(stateMachineBox, true); 这部分代码,就是把回调丢到线程池里去完成,这就让我很懵,这怎么能保证先后顺序呢,于是自己调试走了一遍流程才发现 原来不走这个逻辑,即使走这个逻辑, 那么task任务也是完成了的,始终保持逻辑的先后顺序。所以还是需要实践,想当然是行不通的。

附:不妨看看下面的文章

https://stackoverflow.com/questions/55811416/why-does-continuation-start-before-the-getresult

ExecutionContext 综述

task await async 解析

走进task 任务回调与执行

posted @ 2022-07-10 15:00  果小天  阅读(1401)  评论(0编辑  收藏  举报