Async Await初探

来源:互联网 发布:a字裙淘宝 编辑:程序博客网 时间:2024/06/05 19:28

异步方法的定义约束
首先要明白的一点,就是async/await是不会主动创建线程(Task)的,创建线程的工作还是交给程序员来完成;async/await说白了就只是用来提供阻塞调用点的关键字而已。

因此,如果我们要定义一个异步方法,那么至少要保证:

  1. 在异步方法的调用中会出现新的线程(Task),无论调用层数有多深;
  2. 一个新线程(Task)应该有且仅有一个阻塞调用点;
  3. 异步方法嵌套调用的时候, 每个嵌套调用的异步方法内部至少要调用一个异步方法或者await一个返回值为Task的同步方法。

    async/await的使用场景
    async/await用于异步操作。
    在使用C#编写GUI程序的时候,如果有比较耗时的操作(如图片处理、数据压缩等),我们一般新开一个线程把这些工作交给这个线程处理,而不放到主线程中进行操作,以免阻塞UI刷新,造成程序假死。
    async/await通过对方法进行修饰把C#中的方法分为同步方法和异步方法两类,异步方法命名约定以Async结尾。但是需要注意的是,在调用异步方法的时候,并非一定是以异步方式来进行调用,只有指定了以await为修饰前缀的方法调用才是异步调用。

namespace ConsoleApp1{    class Program    {        static void Main(string[] args)        {            TestMain();        }        static void TestMain()        {            Console.Out.Write("Start\n");            GetValueAsync();            Console.Out.Write("End\n");            Console.ReadKey();        }        static async Task GetValueAsync()        {            await Task.Run(()=>            {                Thread.Sleep(1000);                for(int i = 0; i < 5; ++i)                {                    Console.Out.WriteLine(String.Format("From task : {0}", i));                }            });            Console.Out.WriteLine("Task End");        }    }}

结果:
Start
End
From task : 0
From task : 1
From task : 2
From task : 3
From task : 4
Task End
//
下面来分析该程序的执行流程:

Main()调用TestMain(),执行流转入TestMain();
打印Start
调用GetValueAsync(),执行流转入GetValueAsync(),注意此处是同步调用;
执行Task.Run(),生成一个新的线程并执行,同时立即返回一个Task对象;
由于调用Task.Run()时,是以await作为修饰的,因此是一个异步调用,上下文环境保存第4步中返回的Task对象,在此处发生调用流阻塞,而当前的调用语句便是调用流阻塞点,于是发生调用流阻塞返回,执行流回到AysncCall()的GetValueAsync()处,并执行下一步;
第5步之后就不好分析了,因为此时已经新建了一个线程用来执行后台线程,如果计算机速度够快,那么由于新建的线程代码中有一个Thread.Sleep(1000);,因此线程会被阻塞,于是主线程会赶在新建的线程恢复执行之前打印End然后Console.ReadKey()。在这里我假设发生的是这个情况,然后进入下面的步骤。

新的线程恢复执行,打印0 1 2 3 4 5,线程执行结束,Task对象的IsCompleted变成true;
此时执行流(强制被)跳转到调用流阻塞点,即从调用流阻塞点恢复执行流,发生了调用流阻塞异步完成跳转,于是打印Task End;
程序执行流结束;
仔细研究以上流程,可以发现async/await最重要的地方就是调用流阻塞点,这里的阻塞并不是阻塞的线程,而是阻塞的程序执行流。整个过程就像是一个食客走进一间饭馆点完菜,但是厨师说要等半个小时才做好(调用流阻塞),于是先给这个食客开了张单子(调用流阻塞点)让他先去外面逛一圈(调用流阻塞返回),等时间到了会通知他然后他再拿这张票来吃饭(调用流阻塞异步完成跳转);整个过程中这个食客并没有在饭馆做下来等(线程阻塞),而是又去干了别的事情了。在这里,await就是用来指定调用流阻塞点的关键字,而async则是用来标识某个方法可以被调用流阻塞的关键字。

假如不用await?

如果我们不使用await异步调用方法F的话,那么方法F将会被当成同步方法调用,即发生同步调用,这个时候执行流不会遇到调用流阻塞点,因此会直接往下执行,考虑上面的代码如果

    static async Task GetValueAsync()        {            Task.Run(()=>            {                Thread.Sleep(1000);                for(int i = 0; i < 5; ++i)                {                    Console.Out.WriteLine(String.Format("From task : {0}", i));                }            });            Console.Out.WriteLine("Task End");        }

执行流不会在Task.Run()这里停下返回,而是直接“路过”这里,执行后面的语句,打印出Task End,然后和一般的程序一样返回。当然新的线程还是会被创建出来并执行,但是这种情况下的程序就不会去等Task.Run()完成了。在我的计算机上输出的结果如下:

Start
Task End
End
From task : 0
From task : 1
From task : 2
From task : 3
From task : 4

如果异步方法要返回值
而如果要返回值的话,那么就简单地把Task换成Task就行了,其中T是你的返回值的类型。

异步执行示例:

 private async Task A()        {             test();             test2();            Task a = new Task(() => {                Log.Info("Fragment", Thread.CurrentThread.ManagedThreadId + " #8");            });            a.Start();        }        private async Task test()        {            Log.Info("Fragment", Thread.CurrentThread.ManagedThreadId + " #1");            await Task.Run(() =>            {                Log.Info("Fragment", Thread.CurrentThread.ManagedThreadId + " #2");                Thread.Sleep(3000);                Log.Info("Fragment", Thread.CurrentThread.ManagedThreadId + " #3");            });            Log.Info("Fragment", Thread.CurrentThread.ManagedThreadId + " #4");        }        private async Task test2()        {            Log.Info("Fragment", Thread.CurrentThread.ManagedThreadId + " #5");            await printf();            Log.Info("Fragment", Thread.CurrentThread.ManagedThreadId + " #7");        }        private async  Task printf()        {            Thread.Sleep(3000);            Log.Info("Fragment", Thread.CurrentThread.ManagedThreadId + " #6");        }

结果:
10-18 05:13:40.174 I/Fragment(11108): 1 #0
10-18 05:13:40.183 I/Fragment(11108): 1 #1
10-18 05:13:40.232 I/Fragment(11108): 6 #2
10-18 05:13:40.238 I/Fragment(11108): 1 #5
10-18 05:13:43.233 I/Fragment(11108): 6 #3
10-18 05:13:43.239 I/Fragment(11108): 1 #6
10-18 05:13:43.241 I/Fragment(11108): 1 #7 thread.
10-18 05:13:43.244 I/Fragment(11108): 7 #8
10-18 05:13:43.256 I/Fragment(11108): 1 #4
触发await的代码没有阻塞线程而是执行下面的函数,等await区域执行

如果加上await

  private async Task A()        {            await test();            await test2();            Task a = new Task(() => {                Log.Info("Fragment", Thread.CurrentThread.ManagedThreadId + " #8");            });            a.Start();        }

结果:
0-18 05:05:01.274 I/Fragment(10678): 1 #0
10-18 05:05:01.274 I/Fragment(10678): 1 #1
10-18 05:05:01.274 I/Fragment(10678): 7 #2
10-18 05:05:04.275 I/Fragment(10678): 1 #3
10-18 05:05:04.275 I/Fragment(10678): 1 #4
10-18 05:05:07.277 I/Fragment(10678): 1 #5
10-18 05:05:07.277 I/Fragment(10678): 1 #6
10-18 05:05:07.277 I/Fragment(10678): 6 #8
全部顺序执行了