C# .net async await 学习

让人想犯罪 __ 提交于 2019-12-06 16:41:21

 async/await简单介绍

在处理比较耗时的操作(如图片处理、数据压缩、http请求等)传统的异步方法是直接使用Thread或者Task进行操作,在复杂的应用编写中可能会出现回调的问题,因此C#目前主要推荐使用async/await来进行异步操作。也就是async/await主要用来异步回调问题, 而真正的异步操作还是用Task。

返回值

通常返回 Task 或 Task<TResult>。 在异步方法中,await 运算符应用于通过调用另一个异步方法返回的任务

如果方法包含指定 TResult 类型操作数的 return 语句,将 Task<TResult> 指定为返回类型,如果方法不含任何 return 语句或包含不返回操作数的 return 语句,将 Task 用作返回类型

异步方法也可以具有 void 返回类型。但是不推荐使用,因为无法等待具有 void 返回类型的异步方法,并且无效返回方法的调用方捕获不到异步方法引发的任何异常,而且也违背了我们使用他的初衷--解决异步回调问题

还需要注意:异步方法既不能声明任何 in、ref 或 out 参数,也不能具有引用返回值,但它可以调用具有此类参数的方法

实例

假如现在要做饭,需要做米饭2秒,做汤2秒;同步的方法就是先做米饭等待2秒,然后做汤等待2秒;异步的方法,米饭和汤同时做,一共花2秒

代码如下

class Program
    {
        private static Stopwatch stopwatch = new Stopwatch();
        static void Main(string[] args)
        {
            stopwatch.Start();
            DoCook();
            Console.ReadLine();
        }
        /// <summary>
        /// Cooking
        /// </summary>
        /// <returns></returns>
        static async Task DoCook()
        {
            Console.WriteLine("Cook Start: " + stopwatch.ElapsedMilliseconds.ToString());
            //case 1 异步Cooking
            var rice = DoRice();
            var soup = DoSoup();
            await rice;
            await soup;
            Console.WriteLine($"Cook End: {stopwatch.ElapsedMilliseconds.ToString() } -  {(rice.Result + soup.Result).ToString()}");
        }

        /// <summary>
        /// 做米饭,可以独立做
        /// </summary>
        /// <returns></returns>
        static async Task<int> DoRice()
        {
            Console.WriteLine("DoRice Start: " + stopwatch.ElapsedMilliseconds.ToString());
            var rice = 0;
            await Task.Run(() =>
            {
                Thread.Sleep(2000);
                rice = 100;
            });
            Console.WriteLine("DoRice End: " + stopwatch.ElapsedMilliseconds.ToString());
            return rice;
        }

        /// <summary>
        /// 做汤,可以独立做
        /// </summary>
        /// <returns></returns>
        static async Task<int> DoSoup()
        {
            Console.WriteLine("DoSoup Start: " + stopwatch.ElapsedMilliseconds.ToString());
            var soup = 0;
            await Task.Run(() =>
            {
                Thread.Sleep(2000);
                soup = 100;
            });
            Console.WriteLine("DoSoup End: " + stopwatch.ElapsedMilliseconds.ToString());
            return soup;
        }
    }

 返回的结果

 

 现在你女朋友很作,她非得吃蛋炒饭,蛋炒饭必须先做饭,假如做汤2秒,做蛋炒饭2秒钟,但是必须等米饭先做好

 

定义一个做蛋炒饭的方法

 /// <summary>
        /// 做蛋炒饭,需要先做米饭
        /// </summary>
        /// <returns></returns>
        static async Task<int> DoEggRice()
        {
            Console.WriteLine("DoEggRice Start: " + stopwatch.ElapsedMilliseconds.ToString());
            var rice = await DoRice();
            var eggRice = 0;
            await Task.Run(() =>
            {
                Thread.Sleep(2000);
                eggRice = rice + 100;
            });
            Console.WriteLine("DoEggRice End: " + stopwatch.ElapsedMilliseconds.ToString());
            return eggRice;
        }

然后开始

        /// <summary>
        /// Cooking
        /// </summary>
        /// <returns></returns>
        static async Task DoCook()
        {
            Console.WriteLine("Cook Start: " + stopwatch.ElapsedMilliseconds.ToString());//case 3 异步EggCooking
            var eggRice = DoEggRice();
            var soup = DoSoup();
            await eggRice;
            await soup;
            Console.WriteLine($"Cook End: {stopwatch.ElapsedMilliseconds.ToString() }  - {(eggRice.Result + soup.Result).ToString()}");
        }

结果如下

 

 

注意:

  1. 若某个函数F的函数体中需要使用await关键字的函数必须以async标记,进一步导致需要使用await调用F的那个函数也必须以async标记的情况
  2. await 表达式:用于异步方法内部,指出需要异步执行的任务。一个异步方法可以包含多个 await 表达式(不存在 await 表达式的话 IDE 会发出警告)
  3. var rice = DoRice();  rice.Result 属性为阻止属性。 如果你在其任务完成之前尝试访问它,当前处于活动状态的线程将被阻止,直到任务完成且值为可用。 在大多数情况下,应通过使用 await 访问此值,而不是直接访问属性

引用问答

异步一定能提高效率吗?
不一定。异步本质上还是多线程,只是简化多线程的实现方式。至于使用多线程编程时能否提高程序执行效率,取决于 CPU 核心数,计算任务的复杂度以及该项任务本身是否适合被切分为并行计算模块。过于频繁地将不适合并行计算的任务拆分成异步编程中去,反而会导致密集计算性能的下降,因为此时线程池会疲于应对大量的线程调度操作。
有 async 一定要有 await 吗?

不一定。在标记为 async 的方法中,不必须出现 await 关键字,只是若没有 await 关键字,这个方法不是真正意义上的异步方法,它会与普通方法一样是同步执行的。编译器不会报错,但会给出提示。

相反,若要使用 await 关键字,则必须在方法签名中包含 async 关键字。否则 await 将被当做标识符,而不能被当做一个关键字来处理。也就是说,当一个方法的签名中不包含 async 关键字时,你甚至可以在方法体中把 await 作为变量名。但这种操作是极其不推荐的,很容易造成误导。

异步方法的名称一定要以「Async」为结尾吗?

不一定。这只是习惯问题,就跟微软推荐所有的自定义特性后面都以「Attributes」为结尾一样,这不是必须的,只是如果大家都这样做了,理解起来更加方便一些。具体情况取决于不同场合下的规范要求。

使用 Task 并且 Run 了之后就实现异步了吗?

不是,这只是进行了一次多线程操作,后面的语句还是同步执行的。直到遇见 await 关键字,随着控制权的返回,才真正能实现异步。

异步是线程安全的吗?

理论上是的,这也是为什么异步编程模型能够极大地简化传统多线程操作所带来的各种问题的一大原因。尽管 await 所指的对象运行在其他线程上,但其后的语句还是会在原始线程上被执行。更深层次地说,后续的语句实际上是使用 Task 的 ContinueWith 方法来实现的。所以我们大可以放心的在异步方法中修改诸如 UI 元素等由主线程管理的资源。

但是,异步编程模型只是简化了这个过程,而不能替代我们解决具体的数据同步问题。如果在 await 之后有对其他共享资源的访问,而在 await 获取执行结果之前,这些资源已经被其他线程修改,那么 await 后续语句执行时所面对的数据内容将是不可预测的。

异步一定是返回控制权与等待结果同时进行的吗?

第一时间返回控制权是一定的,而等待与否要看任务执行的状态。当程序遇到 await 关键字时,如果 Task 所指代的对象以极快的速度完成,那么异步方法内部就会以同步执行的方式继续向后执行 await 语句后面的操作,不会产生等待。只有当 Task 没有执行完毕时,才会进行等待

参考文献

https://blog.csdn.net/qc530167365/article/details/83108848

https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/async/

https://www.jianshu.com/p/1136e79d96e6

https://www.jianshu.com/p/8ea7ed4a2493

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!