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能够让我们像写同步代码一样写异步代码,如果一个方法是异步的,也就是说用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