Task async和await解析
探究學習一下task內部async和await的執行機制。本文是用dnspy進行原始碼探索。
用一個例子來解析具體的執行機制。首先建立一個控制檯程式,在控制檯程式中新增一個Test類,Test類中實現一個Say的方法,包含一個輸入和一個輸出。
class Program { static async Task Main(string[] args) { var result = await new Test().Say("hello"); Console.ReadKey(); } } public class Test { public async Task<string> Say(string name) { //await之前的程式碼邏輯 Console.WriteLine("before say"); await Task.Delay(1000); //await之後的程式碼邏輯 Console.WriteLine("after say"); return name; } }
對編譯好的dll檔案進行反編譯,發現原先程式碼裡邊的async和await已經消失了,取而代之的是一個密封類。
public class Test { // Token: 0x06000004 RID: 4 RVA: 0x000020C0 File Offset: 0x000002C0 [DebuggerStepThrough] public Task<string> Say(string name) { Test.<Say>d__0 <Say>d__ = new Test.<Say>d__0(); <Say>d__.<>4__this = this; <Say>d__.name = name; <Say>d__.<>t__builder = AsyncTaskMethodBuilder<string>.Create(); <Say>d__.<>1__state = -1; <Say>d__.<>t__builder.Start<Test.<Say>d__0>(ref <Say>d__); return <Say>d__.<>t__builder.Task; } // Token: 0x06000005 RID: 5 RVA: 0x0000210B File Offset: 0x0000030B public Test() { } // Token: 0x02000005 RID: 5 [CompilerGenerated] private sealed class <Say>d__0 : IAsyncStateMachine { // Token: 0x06000009 RID: 9 RVA: 0x0000220E File Offset: 0x0000040E public <Say>d__0() { } // Token: 0x0600000A RID: 10 RVA: 0x00002218 File Offset: 0x00000418 void IAsyncStateMachine.MoveNext() { int num = this.<>1__state; string result; try { TaskAwaiter awaiter; if (num != 0) { Console.WriteLine("before say"); awaiter = Task.Delay(1000).GetAwaiter(); if (!awaiter.IsCompleted) { this.<>1__state = 0; this.<>u__1 = awaiter; Test.<Say>d__0 <Say>d__ = this; this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, Test.<Say>d__0>(ref awaiter, ref <Say>d__); return; } } else { awaiter = this.<>u__1; this.<>u__1 = default(TaskAwaiter); this.<>1__state = -1; } awaiter.GetResult(); Console.WriteLine("after say"); result = this.name; } catch (Exception exception) { this.<>1__state = -2; this.<>t__builder.SetException(exception); return; } this.<>1__state = -2; this.<>t__builder.SetResult(result); } // Token: 0x0600000B RID: 11 RVA: 0x000022F4 File Offset: 0x000004F4 [DebuggerHidden] void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine) { } // Token: 0x04000007 RID: 7 public int <>1__state; // Token: 0x04000008 RID: 8 public AsyncTaskMethodBuilder<string> <>t__builder; // Token: 0x04000009 RID: 9 public string name; // Token: 0x0400000A RID: 10 public Test <>4__this; // Token: 0x0400000B RID: 11 private TaskAwaiter <>u__1; } }
轉變的方法大致如下:
原先的async方法會變成兩部分內容,會產生一個與原來方法名一致但不帶有async的方法,第二部分會產生一個密封類,根據反編譯的結果來看,名字略顯奇怪,此處暫定為AsyncStateMachine來指代非同步狀態機這個密封類,然後將原先async方法中的程式碼邏輯轉移到非同步狀態機的MoveNext之中。
編譯後方法執行主要做了以下幾個步驟:
1.初始化一個非同步狀態機machine
2.初始化一個AsyncTaskMethodBuilder的例項,賦予machine.builder
3.設定非同步狀態機的狀態為-1,將類傳入到狀態機內部
4.呼叫machine.builder
5.返回machine.builder.Task
AsyncTaskMethodBuilder.Create()
在第二步中僅僅做了個例項化操作,具體程式碼如下
public struct AsyncTaskMethodBuilder
{
// Token: 0x0600363D RID: 13885 RVA: 0x0015277C File Offset: 0x0015157C
public static AsyncTaskMethodBuilder Create()
{
return default(AsyncTaskMethodBuilder);
}
}
AsyncTaskMethodBuilder.Start()
在Start方法中使用的是AsyncMethodBuilderCore.Start方法,先獲取當前執行緒的_executionContext和_synchronizationContext,再執行傳入的非同步狀態機的MoveNext方法,即真正的邏輯程式碼,最後切換回開始獲取的_executionContext和_synchronizationContext。
public static void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
if (stateMachine == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
}
Thread currentThread = Thread.CurrentThread;
Thread thread = currentThread;
ExecutionContext executionContext = currentThread._executionContext;
ExecutionContext executionContext2 = executionContext;
SynchronizationContext synchronizationContext = currentThread._synchronizationContext;
try
{
stateMachine.MoveNext();
}
finally
{
SynchronizationContext synchronizationContext2 = synchronizationContext;
Thread thread2 = thread;
if (synchronizationContext2 != thread2._synchronizationContext)
{
thread2._synchronizationContext = synchronizationContext2;
}
ExecutionContext executionContext3 = executionContext2;
ExecutionContext executionContext4 = thread2._executionContext;
if (executionContext3 != executionContext4)
{
ExecutionContext.RestoreChangedContextToThread(thread2, executionContext3, executionContext4);
}
}
}
IStateMachine.MoveNext()
再來看看stateMachine.MoveNext這個方法,選取開頭例子的Say實現的非同步狀態機中的MoveNext方法。第一次呼叫首先會對狀態state進行判斷,執行await之前的邏輯程式碼,對await的Task任務建立一個TaskAwaiter物件,對於一般的Task來講都算是延遲任務,在判斷IsCompleted狀態時都是處於未完成的狀態,會先改變state狀態,然後將這個含有task任務的awaiter方法賦給了Say非同步狀態機中的awaiter(注意awaiter中task指代的是上述Task.Delay(1000)這個task),再呼叫AwaitUnsafeOnCompleted方法後返回
//task.GetAwaiter的作用 返回一個攜帶task的TaskAwaiter物件
public TaskAwaiter GetAwaiter()
{
return new TaskAwaiter(this);
}
//TaskAwaiter的建構函式
internal TaskAwaiter(Task task)
{
this.m_task = task;
}
Say實現的非同步狀態機中的MoveNext方法具體程式碼如下
void IAsyncStateMachine.MoveNext()
{
int num = this.<>1__state;
string result;
try
{
TaskAwaiter awaiter;
if (num != 0)//判斷狀態state
{
Console.WriteLine("before say");
awaiter = Task.Delay(1000).GetAwaiter();
if (!awaiter.IsCompleted)
{
this.<>1__state = 0;
this.<>u__1 = awaiter;
this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, Test.<Say>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("after say");
result = this.name;
}
catch (Exception exception)
{
this.<>1__state = -2;
this.<>t__builder.SetException(exception);
return;
}
this.<>1__state = -2;
this.<>t__builder.SetResult(result);
}
AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted()
在AwaitUnsafeOnCompleted方法中,傳入了awaiter以及非同步狀態機,正常情況下只執行了下面程式碼,一個是GetStateMachineBox方法,另外一個是TaskAwaiter.UnsafeOnCompletedInternal方法。在GetStateMachineBox方法中會捕獲當前執行上下文executionContext,將執行上下文和非同步狀態機放入到stateMachineBox的Context和StateMachine進行儲存。在UnsafeOnCompletedInternal方法中執行m_task.UnsafeSetContinuationForAwait方法,此時的m_task指代的是上述Task.Delay(1000)這個task
IAsyncStateMachineBox stateMachineBox = this.GetStateMachineBox<TStateMachine>(ref stateMachine);
if (default(TAwaiter) != null && awaiter is ITaskAwaiter)
{
ref TaskAwaiter ptr = ref Unsafe.As<TAwaiter, TaskAwaiter>(ref awaiter);
TaskAwaiter.UnsafeOnCompletedInternal(ptr.m_task, stateMachineBox, true);
return;
}
Task.UnsafeSetContinuationForAwait()
在UnsafeSetContinuationForAwait方法中,通過AddTaskContinuation裡邊的Interlocked.CompareExchange方法將包含執行上下文和非同步狀態機的stateMachineBox或者stateMachineBox的action方法放到task.m_continuationObject(注意這個外層的say非同步狀態機已存入到下一層的task中),然後執行任務。
internal void UnsafeSetContinuationForAwait(IAsyncStateMachineBox stateMachineBox, bool continueOnCapturedContext)
{
if (continueOnCapturedContext)
{
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;
}
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;
}
}
if (!this.AddTaskContinuation(stateMachineBox, false))
{
ThreadPool.UnsafeQueueUserWorkItemInternal(stateMachineBox, true);
}
}
上述都是在執行task之前的程式碼,在執行層層task後,在最裡層的task執行完畢後會執行一個Task.TrySetResult方法。這個方法會呼叫FinishContinuations方法,FinishContinuations方法會將task.m_continuationObject(say非同步狀態機)重新取出來,放入到RunContinuations中,使用AwaitTaskContinuation.RunOrScheduleAction方法呼叫這個上層的非同步狀態機的MoveNext方法。
internal bool TrySetResult()
{
if (this.AtomicStateUpdate(83886080, 90177536))
{
Task.ContingentProperties contingentProperties = this.m_contingentProperties;
if (contingentProperties != null)
{
this.NotifyParentIfPotentiallyAttachedTask();
contingentProperties.SetCompleted();
}
this.FinishContinuations();
return true;
}
return false;
}
internal void FinishContinuations()
{
object obj = Interlocked.Exchange(ref this.m_continuationObject, Task.s_taskCompletionSentinel);
if (obj != null)
{
this.RunContinuations(obj);
}
}
//RunContinuations部分程式碼
private void RunContinuations(object continuationObject)
{
TplEventSource tplEventSource = TplEventSource.Log;
if (!tplEventSource.IsEnabled())
{
tplEventSource = null;
}
if (AsyncCausalityTracer.LoggingOn)
{
AsyncCausalityTracer.TraceSynchronousWorkStart(this, CausalitySynchronousWork.CompletionNotification);
}
bool flag = (this.m_stateFlags & 64) == 0 && RuntimeHelpers.TryEnsureSufficientExecutionStack();
IAsyncStateMachineBox asyncStateMachineBox = continuationObject as IAsyncStateMachineBox;
if (asyncStateMachineBox != null)
{
AwaitTaskContinuation.RunOrScheduleAction(asyncStateMachineBox, flag);
Task.LogFinishCompletionNotification();
return;
}
Action action = continuationObject as Action;
if (action != null)
{
AwaitTaskContinuation.RunOrScheduleAction(action, flag);
Task.LogFinishCompletionNotification();
return;
}
//省略部分....
}
回過頭來再看看Say實現的非同步狀態機中的MoveNext的程式碼。在第二次執行MoveNext時,執行await後半部分邏輯程式碼,設定狀態碼,然後執行SetResult方法,這個方法內部會呼叫上面的Task.TrySetResult方法,從而接著回撥用上層非同步狀態機的MoveNext方法。
void IAsyncStateMachine.MoveNext()
{
int num = this.<>1__state;
string result;
try
{
TaskAwaiter awaiter;
if (num != 0)//判斷狀態state
{
Console.WriteLine("before say");
awaiter = Task.Delay(1000).GetAwaiter();
if (!awaiter.IsCompleted)
{
this.<>1__state = 0;
this.<>u__1 = awaiter;
this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, Test.<Say>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("after say");
result = this.name;
}
catch (Exception exception)
{
this.<>1__state = -2;
this.<>t__builder.SetException(exception);
return;
}
this.<>1__state = -2;
this.<>t__builder.SetResult(result);
}
整個例子實現的流程大致如下
最終總結
在async和await生成的task執行時,會生成一個非同步狀態機,在這個非同步狀態機內部會生成下個階段要執行task的Taskawaiter物件,然後抓取當前task的上下文,和當前的非同步狀態機存入到IAsyncStateMachineBox中,再將該machineBox儲存到Taskawaiter的m_continuationObject中。在下個階段task執行完後,回撥m_continuationObject裡邊非同步狀態機的MoveNext,層層往上回調。