C#基于任务的异步编程基础(二)之async和await创建异步方法

在上一篇文章《C#基于任务的异步编程基础(一)之Task管理多线程》中介绍了Task关键字的基本用法,这篇文章将主要介绍async关键字。

一、async的背后

当方法使用async标识之后,和普通方法有了什么区别,在async背后,编译器做了什么?

1.当使用async标识之后,编译器会理解为这个方法里面可能会用到await关键字,编译器将会在状态机中编译此方法,当方法执行到await关键字时会处于挂起的状态直到该await标记的异步动作完成后才恢复继续执行方法后面的动作。

2.编译器会将运行解析方法的结果存储到返回类型中,通常是Task或者Task<TResult>,如果返回值为void,会将可能出现的异常存储到上下文中。

3.async标识之后,并不会影响或者改变方法同步或者异步执行,在当前线程中一直同步执行方法体内的代码,直到遇见await关键字,也就是说真正异步执行的是await关键字而非async关键字,async仅仅是表示该方法内会有异步执行的方法。

4.当是使用Task存储调用async标识的方法结果时会立即返回,不会阻塞线程,直至遇到.Reslut才会阻塞线程,因为需要等待执行结果

二、不使用await关键字

不使用await关键字,仅仅使用async标识方法,编译器会给出提醒,但方法会正常以同步的方式运行。如下代码:

        static void Main(string[] args)
        {
            string tid = Thread.CurrentThread.ManagedThreadId.ToString();//当前线程id
            Console.WriteLine(DateTime.Now.ToString() + " 主线程id:" + tid + "返回值:" + WriteNameAsync("小李").Result);
            Console.ReadKey();
        }
        private static async Task<string> WriteNameAsync(string name)
        {
            string tid = Thread.CurrentThread.ManagedThreadId.ToString();//当前线程id
            Console.WriteLine(DateTime.Now.ToString() + " 当前线程id:" + tid + " name:" + name);
            return name;
        }

执行结果如下:

可以看到当前线程和方法内的线程是同一个。

三、await关键字的使用

上文说了,使用async标记后的方法,当方法内有await关键字时,执行到await关键字时会处于挂起的状态直到该await标记的异步动作完成后才恢复继续执行方法后面的动作。下面就使用代码验证,如下代码:

        static void Main(string[] args)
        {
            string tid = Thread.CurrentThread.ManagedThreadId.ToString();//当前线程id
            Console.WriteLine(DateTime.Now.ToString() + " 主线程id:" + tid + "返回值:" + WriteNameAsync("小李").Result);
            Console.ReadKey();
        }
        private static async Task<string> WriteNameAsync(string name)
        {
            string tid = Thread.CurrentThread.ManagedThreadId.ToString();//当前线程id
            Console.WriteLine("执行WriteNameAsync方法:" + DateTime.Now.ToString() + " 当前线程id:" + tid + " name:" + name);
            var task = await Task.Run(() => WriteName("小明"));
            Console.WriteLine("执行WriteNameAsync方法:" + DateTime.Now.ToString() + " 当前线程id:" + tid + " name:" + name);
            return task;
        }
        private static string WriteName(string name)
        {
            Thread.Sleep(3000);//当前线程等待三秒
            string tid = Thread.CurrentThread.ManagedThreadId.ToString();//当前线程id
            Console.WriteLine("执行WriteName方法:" + DateTime.Now.ToString() + " 当前线程id:" + tid + " name:" + name);
            return name;
        }

执行结果如图:

从上面得出结论:遇见await关键字事,主线程会等待挂起,直到await标识异步的方法执行完毕才会继续执行方法后的动作

四、await意义何在

从上面代码示例中可以看出,使用async关键字标识方法内可能使用await关键字,假如使用了await关键字,那么即使使用Task创建了一个新线程异步执行,但async标识的方法也得异步方法执行结束后才继续执行,那么这样和同步执行又有何区别?这样使用await关键字的意义又在哪里?

使用await关键字的意义在于,当判断某个方法可能需要耗时较长时,我们可以把它丢给Task,创建一个新的线程去执行,而我们的主线程可以继续做其他的事,这点理解起来可能和上文所述等待await异步方法执行结束才执行后续代码相违背。其实不然,这点请注意上文所述:当是使用Task存储调用async标识的方法结果时会立即返回

将主线程方法修改成以下代码时,即可清楚理解:

        static void Main(string[] args)
        {
            string tid = Thread.CurrentThread.ManagedThreadId.ToString();//当前线程id
            Console.WriteLine(DateTime.Now.ToString() + " 主线程id:" + tid + "主线程即将执行异步方法");
            Task<string> task = WriteNameAsync("小李");
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine(DateTime.Now.ToString() + " 主线程id:" + tid + "主线程执行异步方法后的动作:" + i);
            }
            Console.WriteLine(DateTime.Now.ToString() + " 主线程id:" + tid + "主线程执行异步方法结果:"+task.Result);
            Console.ReadKey();
        }

执行结果如下:

结论:

只有灵活使用Task、async、await才能更好的写出优雅简洁的异步编程的代码,单独使用其中一个并不会发挥其应有的作用,熟练的配合使用就需要丰富的经验作为基础。


版权声明:本文为f363641380原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
THE END
< <上一篇
下一篇>>