ASP.NET_探讨 Async 与 Await 异步同步化编程

2 篇文章 0 订阅
2 篇文章 0 订阅

知其然知其所以然 

          在现在日常编码时,我们公司里的新项目,经常会用到 async 与 await 但始终不是很理解这个编程的优势和为什么要使用他,只是会用语法,会使用,今天让我们一起来了解和理解一下 Async,Await 异步同步化编程吧.

 什么是异步编程?

异步编程就是编写几件事情同时发生而不会发生 “阻塞” 或 “等待” 其他事情完成的代码。这与我们的同步编程不同,在同步编程中,一切都会按照编写的顺序发生(方法与方法之间的调用前后,按照顺序执行)这就是同步代码.

C# 同步执行代码   

为了方便我们更快的调试 原60秒的操作现在为6秒,30秒的操作为3秒.

 PS:  定义两个方法,分别模拟获取用户信息需要时间 3秒,与调用WebService的外部接口需要时间6秒. 

        /// <summary>
        ///  模拟调用 WebService 
        /// </summary>
        /// <returns></returns>
        public string GetRunName()
        {
          Task.Delay(6000).Wait();  // 等待6秒
            return "Name";
        }
        /// <summary>
        ///  模拟获取用户信息方法 
        /// </summary>
        /// <returns></returns>
        public string GetRunPhone()
        {
            Task.Delay(3000).Wait(); // 等待3秒
            return "Phone";
        }

  PS:   同步编程来进行调用这两个方法.

        /// <summary>
        /// 同步调用
        /// </summary>
        /// <returns></returns>
        public string GetNameAndPhone()
        {
            string Name = GetRunName(); //  模拟调用 WebService   返回时间为 6秒
            string Phone = GetRunPhone(); // 模拟方法             返回时间为 3秒
            return Name + Phone;
        }

 PS:   在控制台进行调试.

  public class Program
    {
        public static void Main(string[] args)
        {
            AsyncDemo demo = new AsyncDemo(); // 实例化对象

            string BeginDate = DateTime.Now.ToString();  //  记录方法开始时间
            Console.WriteLine("开始 :" + BeginDate);

            demo.GetNameAndPhone(); // 调用方法

            string EndDate = DateTime.Now.ToString(); //  记录方法结束时间
            Console.WriteLine("结束 :" + EndDate);
            Console.WriteLine("计算时间差 :" + DateDiff(Convert.ToDateTime(BeginDate),Convert.ToDateTime(EndDate)));
            Console.Read();
        }

        public static string DateDiff(DateTime dateTime1,DateTime dateTime2)
        {
            string dateDiff = null;
            TimeSpan ts1 = new TimeSpan(dateTime1.Ticks);
            TimeSpan ts2 = new TimeSpan(dateTime2.Ticks);
            TimeSpan ts = ts1.Subtract(ts2).Duration();
            dateDiff = ts.Days.ToString() + "天" + ts.Hours.ToString() + "小时" + ts.Minutes.ToString() + "分钟" + ts.Seconds.ToString() + "秒";
            return dateDiff;
        }
    }

 PS:   同步编程的执行过程为自上向下,逐步执行所以结果为 9 秒 = ( GetRunName() 6秒 + GetRunPhone() 3秒) 。 在调用此方法的过程中有 6 秒 的空闲期,被浪费了,它可以用来做其他的事情,而异步编程就可以改变这种情况.

C# 异步执行代码

        /// <summary>
        ///  模拟调用 WebService - 异步
        /// </summary>
        /// <returns></returns>
        public async Task<string> GetRunNameAsync()
        {
            await Task.Delay(6000); // 等待6秒
            return "T";
        }
        /// <summary>
        ///  模拟获取用户信息方法 
        /// </summary>
        /// <returns></returns>
        public async Task<string> GetRunPhoneAsync()
        {
             await  Task.Delay(3000);  // 等待3秒
            return "Phone";
        }

PS:   异步编程来进行调用这两个方法.

        /// <summary>
        /// 异步调用
        /// </summary>
        /// <returns></returns>
        public async Task<string> GetNameAndPhoneAsync()
        {
            // 暂时挂起执行,执行其他程序
            var Name = GetRunNameAsync();  //  模拟调用 WebService   返回时间为 6 秒
            var Phone = await GetRunPhoneAsync(); // 模拟获取用户信息 返回时间 3 秒
            // 到此,需要知道方法的执行结果.
            var Name2 = await Name;
            return Name2 + Phone;
        }

 PS:  在控制台的 Program.Main 中进行调试,不支持Async关键字,所以我们给他包装一层MainAsync().

 public class Program
    {
        public  static void Main(string[] args)
        {
            AsyncDemo demo = new AsyncDemo(); // 实例化对象

            string BeginDate = DateTime.Now.ToString();  //  记录方法开始时间
            Console.WriteLine("开始 :" + BeginDate);

            //demo.GetNameAndPhone(); // 调用同步方法
            MainAsync(args).GetAwaiter().GetResult(); // 调用异步方法

            string EndDate = DateTime.Now.ToString(); //  记录方法结束时间
            Console.WriteLine("结束 :" + EndDate);
            Console.WriteLine("计算时间差 :" + DateDiff(Convert.ToDateTime(BeginDate),Convert.ToDateTime(EndDate)));
            Console.Read();
        }

        public static  Task MainAsync(string[] args)
        {
            AsyncDemo demo = new AsyncDemo(); // 实例化对象
           return demo.GetNameAndPhoneAsync(); // 调用异步方法
        }


        public static string DateDiff(DateTime dateTime1,DateTime dateTime2)
        {
            string dateDiff = null;
            TimeSpan ts1 = new TimeSpan(dateTime1.Ticks);
            TimeSpan ts2 = new TimeSpan(dateTime2.Ticks);
            TimeSpan ts = ts1.Subtract(ts2).Duration();
            dateDiff = ts.Days.ToString() + "天" + ts.Hours.ToString() + "小时" + ts.Minutes.ToString() + "分钟" + ts.Seconds.ToString() + "秒";
            return dateDiff;
        }
    }

PS:  执行结果为 6秒.速度提升了3秒,如果是 60秒与30秒,那就整整提升了30秒的执行时间.

区别

1. 首先我们将方法改为了异步,加上了 async 关键字,它会告诉我们编译器这个方法可以异步执行.

2.我们使用了 await 关键字修饰了 Name 变量,它告诉我们编译器,最终我们需要 GetRunNameAsync() 方法的结果,但我们不需要阻塞这个调用,而导致程序在死等此方法的返回值.

3.我们将方法的返回类型修改为 Task<string> ,他通知调用者返回的类型为字符串,而不是立即获得,在此期间我们不只是在等待 GetRunNameAsync() 方法的结果,不能进行其他的操作.

即便是这样我们可以依然很模糊 当我们在 “等待” GetRunNameAsync() 方法返回时,我们实际在做什么?

本质上,系统希望执行 GetRunNameAsync() 方法,因为他首先被调用了,但我们在程序中并没有加上 await 等待关键字,不是需要立即知道它的结果,而程序首先调用的是 GetRunNameAsync() 方法,但它又加上了 async 关键字告诉程序此方法是异步方法,此时程序会暂时异步去处理这个方法,而不堵塞当前进程,继续执行 GetRunPhoneAsync() 方法,此方法标记了 await 关键字,所以程序会等待这个执行完成,最后我们使用了 await 关键字来等待了 GetRunNameAsync()  此方法的结果,程序已经异步执行了此方法,所以返回的结果也即时的返回给我们了,所以异步总共用时才6秒,而同步是一个方法执行完了再执行下一个,所以用了9秒.

异步返回类型

如您所见,我们使用异步编程至少对我来说还是有意义的,但是我们在 MainAsync() 用到的 Task 是什么东西呢?

含有 async 关键字的方法中有三种返回类型可以选择:

  • Task:这个类表示一个异步操作,并且可以被等待;
  • Task<T>:这个类表示一个有返回值的异步操作,并且可以被等待;
  • 以上两个必须和 async 关键字共同使用,否则程序会出错;
  • void:如果一个异步方法返回void,它不能被等待。这实际上把方法变成了“fire and forget(阅后即焚)”方法,这样的情况很少出现。进一步,返回void的异步方法的错误处理有点不同,比如shown by Stephen Cleary没有理由使用void作为异步调用的返回类型,除非完全不关心调用是否实际完成。

PS:简而言之,几乎所有的异步方法都会使用 Task 或 Task<T> 作为它们的返回类型。Task 类表示异步操作本身,而不是action的结果。在一个 Task 中调用 await 意味着我们需要等待这个 Task 执行完成,而在 Task<T> 的情况下,需要检索任务返回的值。

潜在问题

大家在进行异步编程时,可能看不到这样戏剧性(50%)的执行速度的提升,同样我们不会对单个方法进行压力测试,只是同时执行他们。事实上,我们如果不正确的设计我们的异步方法,实际上可能会损害程序整体性能!

当我们将一个方法标记为 async 时,编译器在后台会生成一个状态机;这是额外的代码。如果我们编写好稳健的异步代码,创建这种额外结构所需时间不会对我们造成任何影响,因为运行异步的好处超过了我们构建成本机的成本。然而,如果我们 async 方法内没有 await 关键字,方法将会被同步执行,我们还需要花费额外的时间来创建我们的状态机.

还有一个潜在的问题需要我们注意:我们不能从同步方法调用异步方法,因为在所有情况下,asyncawait 应该是一起的,我们需要在所有方法上都有异步,一步异步处处异步,这就是我们为什么要单独的在 AsyncDemo 类中再单独拎出来一个 async 方法的原因。最终这导致了更多的代码,这也意味着更多的东西理论上可以中断,然而,由于我们想要完成的事情有了良好的设计和坚实的理解,拥有额外的代码也会带来性能上的大大提升,所以在我看来这是公平的交易.

细节问题:当我们在一个 async 异步方法 里调用了异步方法 我们在 A 方法中最终没有用 await 关键字来调用 异步方法,那么被调用的 异步方法如果有返回值,我们可能获取不到他的返回值 所以我们也需要用 await 来等待 B 方法的返回结果,这就是我们一步一步,处处异步.

总结

在实现异步的过程中付出一点额外的努力,对于提高我们的应用程序的性能和响应能力有很长的一段路要走,在 .NET中使用 async 和 await 关键字变得容易,我们可以简单、简洁地、快速地实现异步设计.

我在没有自己做过一遍练习和参考其他网站文档时,我对异步编程一无所知,现在最起码我不是一无所知了,希望你也一样!

参考网站:

https://www.cnblogs.com/libiyang/p/12807339.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值