异步编程之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();
}
posted @ 2021-08-30 15:38  liweiyin  阅读(345)  评论(0编辑  收藏  举报