前语
看过不少关于 await 的原理的文章,也知道背面是编译器给转成了状态机完结的,可是详细是怎么完结的,回调又是如何衔接的,一向都没有搞清楚,这次下定决心把源码自己跑了下,总算豁然开朗了
本文的演示代码基于 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) { }
}
}
- 编译后的代码经过我的整理,命名简化了,更简单理解
状态机完结
- 咱们看到实践是生成了一个躲藏的状态机类
StateMachine
- 把状态机的初始状态
_state
设置 -1
-
stateMachine._builder.Start(ref stateMachine);
发动状态机,内部实践调用的便是状态机的MoveNext
办法
-
Task.Run
创立一个使命, 把托付放在Task.m_action
字段,丢到线程池,等候调度
-
使命在线程池内被调度完结后,是怎么回到这个状态机持续履行后续代码的呢?
_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));
}
- 这儿很清楚的看到,测验把状态机目标(实践是状态机的包装类),赋值到
Task.m_continuationObject
, 假如操作失利,则把状态机目标丢进线程池等候调度,这儿为什么这么完结,看一下线程池是怎么履行的就清楚了
线程池完结
- .NET6 的线程池完结,实践是放到了
PortableThreadPool
, 详细调试过程我就不放了,直接说成果便是, 线程池线程从使命行列中拿到使命后都履行了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 , 直接履行
task.ExecuteFromThreadPool
, 更有意思的这个办法是个虚办法,后面阐明
-
ExecuteFromThreadPool
持续追下去,咱们来到了这儿,代码做了简化
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
便是履行了最开始Task.Run(Work)
封装的托付了, 在m_action
字段
-
this.Finish(true);
跟下去会发现会调用FinishStageTwo
设置使命的完结状态,异常等, 持续调用FinishStageThree
就来了重点:FinishContinuations
这个办法便是衔接后续回调的中心
internal void FinishContinuations()
{
object obj = Interlocked.Exchange(ref this.m_continuationObject, Task.s_taskCompletionSentinel);
if (obj != null)
{
this.RunContinuations(obj);
}
}
- 还记得状态机完结么,
Task.m_continuationObject
字段实践存储的便是状态机的包装类,这儿线程池线程也会判别这个字段有值的话,就直接运用它履行后续代码了
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
创立Task
, 把托付放在m_action
字段, 把Task
压入线程池行列,等候调度
-
_builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
测验把状态机目标放在Task.m_continuationObject
字段上,等候线程池线程调度完结使命后运用(用来履行后续),若操作失利,直接把状态机目标压入线程池行列,等候调度
- 线程池线程调度使命完结后,会判别
Task.m_continuationObject
有值,直接履行它的MoveNext
备注
- 状态机完结中,测验修正
Task.m_continuationObject
,可能会失利,就会直接把状态机目标压入线程池, 可是线程池调度,不都是判别是不是Task
类型么, 其实状态机的包装类是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();
}
- 还有便是状态机包装类,重写了
Task.ExecuteFromThreadPool
,所以线程池调用task.ExecuteFromThreadPool
便是直接调用了状态机的MoveNext
了, Soga ^_^
override void ExecuteFromThreadPool(Thread threadPoolThread)
{
this.MoveNext(threadPoolThread);
}
参阅链接
- 关于线程池和异步的更深入的原理,我们可以参阅下面的文章
概述 .NET 6 ThreadPool 完结: https://www.cnblogs.com/eventhorizon/p/15316955.html
.NET Task 揭秘(2):Task 的回调履行与 await: https://www.cnblogs.com/eventhorizon/p/15912383.html
文章转载自:Broadm