.NET6中的await原理浅析
前言
看過不少關于 await 的原理的文章,也知道背后是編譯器給轉成了狀態(tài)機實現(xiàn)的,但是具體是怎么完成的,回調(diào)又是如何銜接的,一直都沒有搞清楚,這次下定決心把源碼自己跑了下,終于豁然開朗了
本文的演示代碼基于 VS2022 + .NET 6
示例
public class Program
{
static int Work()
{
Console.WriteLine("In Task.Run");
return 1;
}
static async Task TestAsync()
{
Console.WriteLine("Before Task.Run");
await Task.Run(Work);
Console.WriteLine("After Task.Run");
}
static void Main()
{
_ = TestAsync();
Console.WriteLine("End");
Console.ReadKey();
}
}
- 很簡單的異步代碼,我們來看下,編譯器把它變成了啥
class Program
{
static int Work()
{
Console.WriteLine("In Task.Run");
return 1;
}
static Task TestAsync()
{
var stateMachine = new StateMachine()
{
_builder = AsyncTaskMethodBuilder.Create(),
_state = -1
};
stateMachine._builder.Start(ref stateMachine);
return stateMachine._builder.Task;
}
static void Main()
{
_ = TestAsync();
Console.WriteLine("End");
Console.ReadKey();
}
class StateMachine : IAsyncStateMachine
{
public int _state;
public AsyncTaskMethodBuilder _builder;
private TaskAwaiter<int> _awaiter;
void IAsyncStateMachine.MoveNext()
{
int num = _state;
try
{
TaskAwaiter<int> awaiter;
if (num != 0)
{
Console.WriteLine("Before Task.Run");
awaiter = Task.Run(Work).GetAwaiter();
if (!awaiter.IsCompleted)
{
_state = 0;
_awaiter = awaiter;
StateMachine stateMachine = this;
_builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
return;
}
}
else
{
awaiter = _awaiter;
_awaiter = default;
_state = -1;
}
awaiter.GetResult();
Console.WriteLine("After Task.Run");
}
catch (Exception exception)
{
_state = -2;
_builder.SetException(exception);
return;
}
_state = -2;
_builder.SetResult();
}
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine) { }
}
}
- 編譯后的代碼經(jīng)過我的整理,命名簡化了,更容易理解
狀態(tài)機實現(xiàn)
-
我們看到實際是生成了一個隱藏的狀態(tài)機類
StateMachine -
把狀態(tài)機的初始狀態(tài)
_state設置 -1 -
stateMachine._builder.Start(ref stateMachine);啟動狀態(tài)機,內(nèi)部實際調(diào)用的就是狀態(tài)機的MoveNext方法 -
Task.Run創(chuàng)建一個任務, 把委托放在Task.m_action字段,丟到線程池,等待調(diào)度 -
任務在線程池內(nèi)被調(diào)度完成后,是怎么回到這個狀態(tài)機繼續(xù)執(zhí)行后續(xù)代碼的呢?
_builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);就是關鍵了, 跟下去,到了如下的代碼:if (!this.AddTaskContinuation(stateMachineBox, false)) { ThreadPool.UnsafeQueueUserWorkItemInternal(stateMachineBox, true); } 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)); }- 這里很清楚的看到,嘗試把狀態(tài)機對象(實際是狀態(tài)機的包裝類),賦值到
Task.m_continuationObject, 如果操作失敗,則把狀態(tài)機對象丟進線程池等待調(diào)度,這里為什么這么實現(xiàn),看一下線程池是怎么執(zhí)行的就清楚了
- 這里很清楚的看到,嘗試把狀態(tài)機對象(實際是狀態(tài)機的包裝類),賦值到
線程池實現(xiàn)
- .NET6 的線程池實現(xiàn),實際是放到了
PortableThreadPool, 具體調(diào)試步驟我就不放了,直接說結果就是, 線程池線程從任務隊列中拿到任務后都執(zhí)行了DispatchWorkItem方法
static void DispatchWorkItem(object workItem, Thread currentThread)
{
Task task = workItem as Task;
if (task != null)
{
task.ExecuteFromThreadPool(currentThread);
return;
}
Unsafe.As<IThreadPoolWorkItem>(workItem).Execute();
}
virtual void ExecuteFromThreadPool(Thread threadPoolThread)
{
this.ExecuteEntryUnsafe(threadPoolThread);
}
-
我們看到, 線程池隊列中的任務都是 object 類型的, 這里進行了類型判斷, 如果是 Task , 直接執(zhí)行
task.ExecuteFromThreadPool, 更有意思的這個方法是個虛方法,后面說明 -
ExecuteFromThreadPool繼續(xù)追下去,我們來到了這里,代碼做了簡化private void ExecuteWithThreadLocal(ref Task currentTaskSlot, Thread threadPoolThread = null) { this.InnerInvoke(); this.Finish(true); } virtual void InnerInvoke() { Action action = this.m_action as Action; if (action != null) { action(); return; } } -
很明顯
this.InnerInvoke就是執(zhí)行了最開始Task.Run(Work)封裝的委托了, 在m_action字段 -
this.Finish(true);跟下去會發(fā)現(xiàn)會調(diào)用FinishStageTwo設置任務的完成狀態(tài),異常等, 繼續(xù)調(diào)用FinishStageThree就來了重點:FinishContinuations這個方法就是銜接后續(xù)回調(diào)的核心internal void FinishContinuations() { object obj = Interlocked.Exchange(ref this.m_continuationObject, Task.s_taskCompletionSentinel); if (obj != null) { this.RunContinuations(obj); } } -
還記得狀態(tài)機實現(xiàn)么,
Task.m_continuationObject字段實際存儲的就是狀態(tài)機的包裝類,這里線程池線程也會判斷這個字段有值的話,就直接使用它執(zhí)行后續(xù)代碼了void RunContinuations(object continuationObject) { var asyncStateMachineBox = continuationObject as IAsyncStateMachineBox; if (asyncStateMachineBox != null) { AwaitTaskContinuation.RunOrScheduleAction(asyncStateMachineBox, flag2); return; } } static void RunOrScheduleAction(IAsyncStateMachineBox box, bool allowInlining) { if (allowInlining && AwaitTaskContinuation.IsValidLocationForInlining) { box.MoveNext(); return; } }
總結
-
Task.Run創(chuàng)建Task, 把委托放在m_action字段, 把Task壓入線程池隊列,等待調(diào)度 -
_builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);嘗試把狀態(tài)機對象放在Task.m_continuationObject字段上,等待線程池線程調(diào)度完成任務后使用(用來執(zhí)行后續(xù)),若操作失敗,直接把狀態(tài)機對象壓入線程池隊列,等待調(diào)度 - 線程池線程調(diào)度任務完成后,會判斷
Task.m_continuationObject有值,直接執(zhí)行它的MoveNext
備注
-
狀態(tài)機實現(xiàn)中,嘗試修改
Task.m_continuationObject,可能會失敗,
就會直接把狀態(tài)機對象壓入線程池, 但是線程池調(diào)度,不都是判斷是不是Task類型么, 其實狀態(tài)機的包裝類是Task的子類,哈哈,是不是明白了class AsyncStateMachineBox<TStateMachine> : Task<TResult>, IAsyncStateMachineBox where TStateMachine : IAsyncStateMachine static void DispatchWorkItem(object workItem, Thread currentThread) { Task task = workItem as Task; if (task != null) { task.ExecuteFromThreadPool(currentThread); return; } Unsafe.As<IThreadPoolWorkItem>(workItem).Execute(); }
-
還有就是狀態(tài)機包裝類,重寫了
Task.ExecuteFromThreadPool,所以線程池調(diào)用task.ExecuteFromThreadPool就是直接調(diào)用了狀態(tài)機的MoveNext了, Soga ^_^override void ExecuteFromThreadPool(Thread threadPoolThread) { this.MoveNext(threadPoolThread); }
參考鏈接
- 關于線程池和異步的更深刻的原理,大家可以參考下面的文章
概述 .NET 6 ThreadPool 實現(xiàn): https://www.cnblogs.com/eventhorizon/p/15316955.html
.NET Task 揭秘(2):Task 的回調(diào)執(zhí)行與 await: https://www.cnblogs.com/eventhorizon/p/15912383.html
總結
以上是生活随笔為你收集整理的.NET6中的await原理浅析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Sealos 云操作系统一键集成 run
- 下一篇: 将ECharts图表插入到Word文档中