• Async Programming


    上一篇讲了这么多,其实说的就是一个事,return会被编译器重写成SetResult,所以如果我们的异步函数返回的是一个Task<int>,代码就要改成这样:

    using System;
    using System.Runtime.CompilerServices;
    using System.Threading.Tasks;
    
    namespace StateMachineDemo
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine(FooAsync2().Result);
            }
    
            public static async Task&lt;int&gt; FooAsync()
            {
                await Task.Delay(10000);
                return 42;
            }
    
            public static Task&lt;int&gt; FooAsync2()
            {
                var stateMachine = new FooAsyncStateMachine();
                stateMachine.MethodBuilder = new AsyncTaskMethodBuilder&lt;int&gt;();
    
                stateMachine.MethodBuilder.Start(ref stateMachine);
    
                return stateMachine.MethodBuilder.Task;
            }
    
            public struct FooAsyncStateMachine : IAsyncStateMachine
            {
                public AsyncTaskMethodBuilder&lt;int&gt; MethodBuilder;
                public void MoveNext()
                {
                    MethodBuilder.SetResult(42);
                }
    
                public void SetStateMachine(IAsyncStateMachine stateMachine)
                {
                    throw new NotImplementedException();
                }
            }
        }
    }

    函数的返回值换成了Task<int>,里面返回MethodBuilder.Task,原来的AsyncVoidMethodBuilder对应换成了AsyncTaskMethodBuilder<int>,SetResult这里面return了一个值,就是这样。

    下面来加点料,前面说的都是平凡的异步方法,现在我们真正的来在方法里面await一个东西,比如await Task.Delay(10000);

            public static async Task<int> FooAsync()
            {
                await Task.Delay(10000);
                return 42;
            }

    这个时候状态机就要真正的起作用了,这个代码有两个状态——await执行之前到开始执行、await执行之后,也就是说对于一个有n个await函数,他就有n+1个状态,分别是从开始到第1个await,第i到i+1个await,第n个await到结束。

    对于我们的代码,不妨用一个int来表示状态,0是第一个状态,1是第二个状态,我们先在MoveNext里面把框架写好:

            public struct FooAsyncStateMachine : IAsyncStateMachine
            {
                public AsyncTaskMethodBuilder<int> MethodBuilder;
    
                public int State;
    
                public void MoveNext()
                {
                    if (State == 0)
                    {
                        //await之前以及开始await
                        return;
                    }
    
                    if (State == 1)
                    {
                        //await之后
                        MethodBuilder.SetResult(42);
                        return;
                    }
                }
    
                public void SetStateMachine(IAsyncStateMachine stateMachine)
                {
                    throw new NotImplementedException();
                }
            }

    好了现在看State==0的时候,这里面await之前没有代码,所以我们只需要启动Task.Delay(10000),首先是Task.Delay(10000);表达式的求值:

                public void MoveNext()
                {
                    if (State == 0)
                    {
                        Task.Delay(10000);
                        return;
                    }
    
                    if (State == 1)
                    {
                        //await之后
                        MethodBuilder.SetResult(42);
                        return;
                    }
                }

    这样还不行,我们没法确定Task.Delay什么时候完成,编译器在这里是这样实现的:Task.Delay(10000);返回的是一个Task,对他调用GetAwaiter,拿到awaiter:

                public void MoveNext()
                {
                    if (State == 0)
                    {
                        TaskAwaiter awaiter = Task.Delay(10000).GetAwaiter();
                        //注册回调什么的
                        return;
                    }
    
                    if (State == 1)
                    {
                        //await之后
                        MethodBuilder.SetResult(42);
                        return;
                    }
                }

    如果我们直接这么写

                    if (State == 0)
                    {
                        TaskAwaiter awaiter = Task.Delay(10000).GetAwaiter();
                        awaiter.GetResult();
                        return;
                    }

    GetResult是个阻塞调用,卡住控制流就不好了,所以GetResult要在Task确定完成的时候,也就是State==1的时候(State被谁设为1我们之后讲),也就是在第二个if里面,GetResult

                    if (State == 1)
                    {
                        //await之后
                        awaiter.GetResult();
                        MethodBuilder.SetResult(42);
                        return;
                    }

    然而你会发现TaskAwaiter的作用域是前一个if块,在这里我们访问不到,所以TaskAwaiter也应该作为类成员而不是方法内的局部变量,顺便说这东西也是一个struct,同样是我们不希望有额外的堆操作开销,最后改好的StateMachine是这样

            public struct FooAsyncStateMachine : IAsyncStateMachine
            {
                public AsyncTaskMethodBuilder<int> MethodBuilder;
    
                public int State;
    
                private TaskAwaiter awaiter;
    
                public void MoveNext()
                {
                    if (State == 0)
                    {
                        awaiter = Task.Delay(10000).GetAwaiter();
                        return;
                    }
    
                    if (State == 1)
                    {
                        //await之后
                        awaiter.GetResult();
                        MethodBuilder.SetResult(42);
                        return;
                    }
                }
    
                public void SetStateMachine(IAsyncStateMachine stateMachine)
                {
                    throw new NotImplementedException();
                }
            }

    还没完,第一个if里面是已经让awaiter跑起来了,还需要一个机制让它在完成的时候State变成1,以及让他调用MoveNext,走第二个if,继续后面的控制流。这里有一个简单的优化,我们先检测task有没有完成,如果完成了,那么把State设为1,直接goto到第二个if,如果没有的话,再注册让他完成时调用MoveNext的相关东西:

                    if (State == 0)
                    {
                        awaiter = Task.Delay(10000).GetAwaiter();
                        if (awaiter.IsCompleted)
                        {
                            State = 1;
                            goto state1;
                        }
                        else
                        {
                            //task还没完
                        }
                        return;
                    }
    state1:
                    if (State == 1)
                    {
                        //await之后
                        awaiter.GetResult();
                        MethodBuilder.SetResult(42);
                        return;
                    }
                }

    如果task还没完,我们同样需要把State设为1,这样当然完了的时候,一个什么东西继续调用MoveNext,就能直接执行第二个if块的内容了。关于注册完成时调用MoveNext,这里有个API函数叫做

                    if (State == 0)
                    {
                        awaiter = Task.Delay(10000).GetAwaiter();
                        if (awaiter.IsCompleted)
                        {
                            State = 1;
                            goto state1;
                        }
                        else
                        {
                            State = 1;
                            MethodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
                        }
                        return;
                    }

    StateMachine.AwaitUnsafeOnCompleted,还有一个函数叫AwaitOnCompleted,具体里面有什么差别我也不是很懂,总之编译器重写的时候用的是这个,两个参数,一个是awaiter,要等待完成的东西,一个this,完成之后调用this的MoveNext,逻辑很清楚,可以看到这里用了ref,为了把参数都转移到堆上,因为之后方法就返回栈就回退了,栈上的东西都会销毁。

    看似什么都已经好了(其实并没有),我们运行一下看看:

    image

    哎,挂在了这里,这里我们应该写成这样:

                public void SetStateMachine(IAsyncStateMachine stateMachine)
                {
                    MethodBuilder.SetStateMachine(stateMachine);
                }

    为什么一个method builder要SetStateMachine呢,回去看我们当初写的代码:

            public static Task<int> FooAsync2()
            {
                var stateMachine = new FooAsyncStateMachine();
                stateMachine.MethodBuilder = new AsyncTaskMethodBuilder<int>();
    
                stateMachine.MethodBuilder.Start(ref stateMachine);
    
                return stateMachine.MethodBuilder.Task;
            }

    这里MethodBuilder是获得了一个ref,引用到到栈上的stateMachine,C#的ref你可以理解为指针:最开始,MethodBuilder里面有一个指向栈上stateMachine的指针,这很好,但是前面一步我们用MethodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref this); 调用把state machine本体转移到了堆上,所以原来MethodBuilder里面指向栈上StateMachine的引用(指针)就无效了,所以我们需要给MethodBuilder一个新的堆上的stateMachine的引用,让他持有正确的状态机。

    现在都好了,我在if(State==1)那里下个断点,我们跑一下代码,看看回调是怎么发生的:

    image

    代码停在了这里,看调用堆栈,是另一条独立的线程继续调用了MoveNext,从Delay产生作用的TimerCallback一直向上,最后调用了MoveNext,值得一提的是,主线程被某种同步机制卡住了,因为那时候FooAsync2的结果还没有return出来给它打印,看看并行堆栈监视

    image

    的确是一条独立的线程调用了MoveNext,主线程也被卡住了。

    总结一下,编译器会把第i个await重写为两个部分,一个是在状态i里面的GetAwaiter、测试IsCompleted,有的话直接跳第i+1个状态、没有的话注册回调,另一个是在状态i+1里面的GetResult。

    就这么多,后面复杂的东西,比如AwaitUnsafeOnComplete为什么Unsafe,以及主线程被卡住的机制,我还不怎么懂,这篇主要是讲一下编译器怎么重写最简单的await,大概思路是这样,至于更复杂的await,比如if块里的,try-catch里的,不是很懂也就不说了。

  • 相关阅读:
    log4j 日志配置
    找出两个列表元素中相同的元素
    列表元素去重
    hdu 2149 Public Sale(巴什博弈变形)
    POJ 3169 Layout (差分约束+SPFA)
    hdu 1494 跑跑卡丁车(动态规划)
    hdu 3177 Crixalis's Equipment(贪心)
    HDU 1576 A/B(扩展欧几里德变形)
    POJ 1061青蛙的约会(扩展欧几里德)
    IE6下的CSS多类选择符
  • 原文地址:https://www.cnblogs.com/pointer-smq/p/5489810.html
Copyright © 2020-2023  润新知