zoukankan      html  css  js  c++  java
  • 异步编程之async&await

    async&await定义

    首先async&await是语法糖。是C#5.0后支持的一种异步编程快捷方式。async书写在方法上,表示该方法是一个异步方法,同时,async与await一定是配套使用的,async异步方法的返回类型仅有三种: void,Task,Task<T>方法内部使用await关键字标明开始执行异步代码。 await运算符的操作数通常是以下其中一个 .NET 类型:

    Task、Task<TResult>、ValueTask 或 ValueTask<TResult>

    但是,任何可等待表达式都可以是 await 运算符的操作数。 有关详细信息,请参阅 C# 语言规范中的可等待表达式部分。

    异步方法写法如下:

    public async void WithOutReturn()
            {
                await Task.Factory.StartNew(()=>{
                    Console.WriteLine("task is running....");
                });
                await foreach...   //C#8.0开始支持迭代异步流
                await using...      //C#8.0使用 await using 语句来处理异步可释放对象
            }
            

    执行顺序

    主线程执行async异步方法时,会同步执行await标志前的代码,当遇到await语句时,主线程会返回到调用async语句处执行后续语句,await标志的方法或者语句为异步执行,其后的代码相当于"回调函数",在await执行完后继续执行。

    举例说明:

    同步例子:

     private async static Task Test()
            {
                Console.WriteLine($"当前主线程id={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                {
                    NoReturnNoAwait();
                }
                Console.WriteLine($"Main Thread Task ManagedThreadId= 
               {Thread.CurrentThread.ManagedThreadId}");
                Console.Read();
            }
    
         
            private static void NoReturnNoAwait()
            {
                //主线程执行
                Task task = Task.Run(() =>//启动新线程完成任务
                {
                    Console.WriteLine($"NoReturnNoAwait Sleep3000 before,ThreadId={Thread.CurrentThread.ManagedThreadId}");
                    Thread.Sleep(1000);
                    Console.WriteLine($"NoReturnNoAwait Sleep3000 after,ThreadId={Thread.CurrentThread.ManagedThreadId}");
                });
                
                //主线程执行
                Console.WriteLine($"NoReturnNoAwait Sleep after Task,ThreadId={Thread.CurrentThread.ManagedThreadId}");
            }
    View Code

    async异步方法不带返回值

    异步例子:

         private async static Task Test()
            {
                Console.WriteLine($"当前主线程id={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                {
                    NoReturn();
    
                }
                Console.Read();
            }
            private static async void NoReturn()
            {
                //主线程执行
                Console.WriteLine($"NoReturn Sleep before await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
                TaskFactory taskFactory = new TaskFactory();
                Task task = taskFactory.StartNew(() =>
                {
                    Console.WriteLine($"NoReturn Sleep3000 before,ThreadId={Thread.CurrentThread.ManagedThreadId}");
                    Thread.Sleep(3000);
                    Console.WriteLine($"NoReturn Sleep3000 after,ThreadId={Thread.CurrentThread.ManagedThreadId}");
                });
                await task;
                //主线程到await这里就返回了,执行主线程任务
                //同时task的子线程就开始工作,直到Task完成,然后继续后续任务(后续任务的线程ID不一定是这个子线程,可以是子线程,也可以是其他线程,还可以是主线程) 
                //像什么? 效果上等价于continuewith
                //task.ContinueWith(t =>
                //{
                //    Console.WriteLine($"NoReturn Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
                //});
    
                Console.WriteLine($"NoReturn Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
            }
    View Code

    await标志前的代码是同步执行,await标志的方法是异步执行,await标志的方法后面的代码相当于"回调函数",在await标志的异步方法后面执行。

    async异步方法带返回值Task

     private async static Task Test()
            {
                Console.WriteLine($"当前主线程id={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                {
                  Task t = NoReturnTask();
                  Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
                }
                Console.Read();
            }
      private static async Task NoReturnTask() //在async/await方法里面如果没有返回值,默认返回一个Task
            {
                //这里还是主线程的id
                Console.WriteLine($"NoReturnTask Sleep before await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
    
                Task task = Task.Run(() =>
                {
                    Console.WriteLine($"NoReturnTask Sleep3000 before,ThreadId={Thread.CurrentThread.ManagedThreadId}");
                    Thread.Sleep(1000);
                    Console.WriteLine($"NoReturnTask Sleep3000 after,ThreadId={Thread.CurrentThread.ManagedThreadId}");
                });
                await task;
                Console.WriteLine($"NoReturnTask Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
                //return;
                //return new TaskFactory().StartNew(() => { });  //不能return  没有async才行
            }

    当此处加上return语句后,编译器提示:

    严重性    代码    说明    项目    文件    行    禁止显示状态
    错误    CS1997    由于“AwaitAsyncClass.NoReturnTask()”是返回“Task”的异步方法,因此返回关键字不能后接对象表达式。是否要返回“Task<T>”?    

    在使用async/await异步方法时,如果方法里面如果没有返回值,默认返回一个Task,其表示该异步方法无返回值。

    无返回值的情况下, async Task 等同于async void,两者间的区别是:异步函数签名为async Task(Task<T>)时,函数调用处可以使用await, Task.WhenAny, Task.WhenAll等方式组合使用;而Async Void 则不行。

    接收异步方法的task一般可以这样处理:

    1、阻塞式

     private async static Task Test()
            {
                Console.WriteLine($"当前主线程id={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                {
                  Task t = NoReturnTask();
                  t.Wait();//主线程等待Task的完成  阻塞的【调用Task.Wait()
    Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");

    }
                Console.Read();
            }

    2、非阻塞

     private async static Task Test()
            {
                Console.WriteLine($"当前主线程id={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                {
                  Task t = NoReturnTask();
                 await t;//await后的代码会由线程池的线程执行  非阻塞
                 Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
                }
                Console.Read();
            }

     结果与上面的一致。是因为当主线程执行Test()方法中await t后,返回Test()调用处继续往下执行。Test()方法中Task t = NoReturnTask()在await之前,所以会先执行完毕(虽然开启的是一个子线程),此时test里面的

    Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");

    等同于

    t.ContinueWith(t =>
            {Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");});

    再增加几条打印,修改子线程休眠顺序,结果更加清晰。

        public static void TestShow()
            {
                Test();
                Console.WriteLine($"TestShow Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
                Console.WriteLine("主线程执行Test后,遇await返回,往下执行");
            }
    private async static Task Test()
            {
                Console.WriteLine($"当前主线程id={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
               {
                    Task t = NoReturnTask();
                    await t;//await后的代码会由线程池的线程执行  非阻塞
                    Console.WriteLine($"Thread Task in Test method ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
                }
        Console.Read();
    }
            private static async Task NoReturnTask() //在async/await方法里面如果没有返回值,默认返回一个Task
            {
                //这里还是主线程的id
                Console.WriteLine($"NoReturnTask Sleep before await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
    
                Task task = Task.Run(() =>
                {
                    Thread.Sleep(3000);
                    Console.WriteLine($"NoReturnTask Sleep3000 before,ThreadId={Thread.CurrentThread.ManagedThreadId}");
                    Console.WriteLine($"NoReturnTask Sleep3000 after,ThreadId={Thread.CurrentThread.ManagedThreadId}");
                });
                await task;
                Console.WriteLine($"NoReturnTask Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
                //return;
                // return new TaskFactory().StartNew(() => { });  //不能return  没有async才行
            }
    View Code

    async异步方法带返回值Task<T>

    private async static Task Test()
                {
                    Console.WriteLine($"当前主线程id={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                    Task<long> t = SumAsync();
                    Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
                    long lResult = t.Result;//访问result,阻塞式   主线程等待所有的任务挖成 //如果访问Result,就相当于是同步方法!
                    //t.Wait();//等价于上一行,阻塞式--同步
                    //await t;//非阻塞,
                }
            /// <summary>
            /// 带返回值的Task  
            /// 要使用返回值就一定要等子线程计算完毕
            /// </summary>
            /// <returns>async 就只返回long</returns>
            private static async Task<long> SumAsync()
            {
                Console.WriteLine($"SumAsync 111 start ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
                long result = 0;
                int sum = 5;
                await Task.Run(() =>
                {
                    for (int k = 0; k < sum; k++)
                    {
                        Console.WriteLine($"SumAsync {k} await Task.Run ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
                        Thread.Sleep(1000);
                    }
                    for (long i = 0; i < 999_999_999; i++)
                    {
                        result += i;
                    }
                });
    
                Console.WriteLine($"SumFactory 111   end ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
                await Task.Run(() =>
                {
                    for (int k = 0; k < sum; k++)
                    {
                        Console.WriteLine($"SumAsync {k} await Task.Run ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
                        Thread.Sleep(1000);
                    }
                    for (long i = 0; i < 999999999; i++)
                    {
                        result += i;
                    }
                });
                Console.WriteLine($"SumFactory 111   end ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
    
                await Task.Run(() =>
                {
                    for (int k = 0; k < sum; k++)
                    {
                        Console.WriteLine($"SumAsync {k} await Task.Run ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
                        Thread.Sleep(1000);
                    }
    
                    for (long i = 0; i < 999999999; i++)
                    {
                        result += i;
                    }
                });
    
                Console.WriteLine($"SumFactory 111   end ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
    
                return result;
            }
    View Code

     原理解析

    static void Main(string[] args)
            {
                Test();
                Console.WriteLine($"main thread id={Thread.CurrentThread.ManagedThreadId}");
                Console.ReadKey();
            }
    
            static async void Test()
            {
                await Task.Factory.StartNew(() =>
                {
                    Task.Delay(1000);//当前任务延时1s后执行
                    Console.WriteLine($"execute proc,thread id={Thread.CurrentThread.ManagedThreadId}");
                });
                Console.WriteLine($"after await,thread id={Thread.CurrentThread.ManagedThreadId} ");
            }
    View Code

     通过ILSpy查看其C#代码如下:

    注意:ILSpy 选项->反编译器项要去掉C#5.0(反编译异步方法条目)

        private static void Main(string[] args)
        {
            Test();
            Console.WriteLine($"main thread id={Thread.CurrentThread.ManagedThreadId}");
            Console.ReadKey();
        }
    
        [AsyncStateMachine(typeof(<Test>d__1))]
        [DebuggerStepThrough]
        private static void Test()
        {
            <Test>d__1 stateMachine = new <Test>d__1();
            stateMachine.<>t__builder = AsyncVoidMethodBuilder.Create();
            stateMachine.<>1__state = -1;
            stateMachine.<>t__builder.Start(ref stateMachine);
        }

    Main方法没有变化,Test方法与原来的不同,看来编译器帮我们做了许多事情来通过async,await来实现异步方法。

    这里Test方法主要做了这几件事情:

    1.实例化 <Test>d__1类对象 stateMachine(状态机),并对 t__builder和1__state(状态)赋初值

    2.执行 stateMachine.<>t__builder.Start(ref stateMachine);【实际是调用 AsyncVoidMethodBuilder结构体下的 Start方法】

    我们首先看 <Test>d__1类

    [CompilerGenerated]
        private sealed class <Test>d__1 : IAsyncStateMachine
        {
            public int <>1__state;
    
            public AsyncVoidMethodBuilder <>t__builder;//此处Test同步方法时一个void函数,其对应的method builder为AsyncVoidMethodBuilder
    private TaskAwaiter <>u__1; private void MoveNext() { int num = <>1__state; try { TaskAwaiter awaiter; if (num != 0) { awaiter = Task.Factory.StartNew(delegate { Task.Delay(1000); Console.WriteLine($"execute proc,thread id={Thread.CurrentThread.ManagedThreadId}"); }).GetAwaiter(); if (!awaiter.IsCompleted) { num = (<>1__state = 0); <>u__1 = awaiter; <Test>d__1 stateMachine = this; <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); return; } } else { awaiter = <>u__1; <>u__1 = default(TaskAwaiter); num = (<>1__state = -1); } awaiter.GetResult(); Console.WriteLine($"after await,thread id={Thread.CurrentThread.ManagedThreadId} "); } catch (Exception exception) { <>1__state = -2; <>t__builder.SetException(exception); return; } <>1__state = -2; <>t__builder.SetResult();

     AsyncVoidMethodBuilder结构体下的 Start方法

        public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
    {
    if (stateMachine == null) { throw new ArgumentNullException("stateMachine"); } ExecutionContextSwitcher ecsw = default(ExecutionContextSwitcher); RuntimeHelpers.PrepareConstrainedRegions(); try { ExecutionContext.EstablishCopyOnWriteScope(ref ecsw); stateMachine.MoveNext();// } finally { ecsw.Undo(); } }
    此处泛型约束stateMachine变量是实现IAsyncStateMachine接口的对象,而实际传入的ref stateMachine为实现了
    IAsyncStateMachine接口的<Test>d__1密封类,故stateMachine.MoveNext()实际调用的是<Test>d__1下的MoveNext()方法:

    说明:此处如果awaiter

    // System.Runtime.CompilerServices.AsyncVoidMethodBuilder
    using System.Security;
    using System.Threading.Tasks;
    [SecuritySafeCritical]
    [__DynamicallyInvokable]
    public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine
    {
        try
        {
            AsyncMethodBuilderCore.MoveNextRunner runnerToInitialize = null;
            Action completionAction = m_coreState.GetCompletionAction(AsyncCausalityTracer.LoggingOn ? Task : null, ref runnerToInitialize);//创建action回调函数
            if (m_coreState.m_stateMachine == null)
            {
                if (AsyncCausalityTracer.LoggingOn)
                {
                    AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, Task.Id, "Async: " + stateMachine.GetType().Name, 0uL);
                }
                m_coreState.PostBoxInitialization(stateMachine, runnerToInitialize, null);
            }
            awaiter.UnsafeOnCompleted(completionAction);//Action交给Awaiter,让它在await的操作完成后执行这个Action,后续可以看到这个action委托绑定的函数实际为run,即awaiter操作完毕->action动作执行->run方法开始触发执行
        }
        catch (Exception exception)
        {
            AsyncMethodBuilderCore.ThrowAsync(exception, null);
        }
    }

    此处主要完成两件事:
    一是创建了一个Action,MoveNext方法的信息已经随着stateMachine被封装进去了。
    二是把上面这个Action交给Awaiter,让它在awaiter的操作完成后执行这个Action。

     

     而moveNextRunner.Run方法实际所做的事情就是执行此时状态机的MoveNext方法。

    即执行stateMachine.MoveNext()促使状态机继续进行状态流转,迭代。

    【ExecutionContext上下文后续研究】

    [SecuritySafeCritical]
    internal void Run()
    {
        if (m_context != null)
        {
            try
            {
                ContextCallback callback = InvokeMoveNext;
                ExecutionContext.Run(m_context, callback, m_stateMachine, preserveSyncCtx: true);
            }
            finally
            {
                m_context.Dispose();
            }
        }
        else
        {
            m_stateMachine.MoveNext();
        }
    }
    [SecurityCritical]
    private static void InvokeMoveNext(object stateMachine)
    {
        ((IAsyncStateMachine)stateMachine).MoveNext();
    }
  • 相关阅读:
    常见数据结构图文详解-C++版
    求单链表中环的起点,原理详解
    Qt Creator 整合 python 解释器教程
    Qt 共享库(动态链接库)和静态链接库的创建及调用
    qt creator 中的"提升为..."功能简介
    QT 操作 excel 教程
    网易2017校园招聘算法题c语言实现源码
    c语言版贪吃蛇小游戏
    mysql语法结构
    Spring MVC rest接收json中文格式数据显示乱码
  • 原文地址:https://www.cnblogs.com/shuzhongke/p/15178242.html
Copyright © 2011-2022 走看看