前语

看过不少关于 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;
    }
}

总结

  1. Task.Run创立Task, 把托付放在m_action字段, 把Task压入线程池行列,等候调度
  1. _builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);测验把状态机目标放在Task.m_continuationObject字段上,等候线程池线程调度完结使命后运用(用来履行后续),若操作失利,直接把状态机目标压入线程池行列,等候调度
  1. 线程池线程调度使命完结后,会判别Task.m_continuationObject有值,直接履行它的MoveNext

备注

  1. 状态机完结中,测验修正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

原文链接:www.cnblogs.com/broadm/p/17…