asp.net core 系列 5 项目实战之:NetCore 的 async 和 await(参考自:Microsoft教程)

摘要:
void返回类型主要用在事件处理程序中,一种称为“fireandforget”的活动的方法。与void对比呢,Task可以使用await进行等待新线程执行完毕。async这个关键词其实反而是可以省略的,这个关键词存在的意义是为了向下兼容,为await提供上下文而已。其实真正重要的是await,有没有async反而确实不重要。

十年河东,十年河西,莫欺少年穷

学无止境,精益求精

1、简介

从 VS 2012 开始,新引入了一个简化的方法,称为异步编程。我们在 >= .NETFRM 4.5 中和 Windows 运行时中使用异步,编译器它会帮助了我们降低了曾经进行的高难度异步代码编写的工作,但逻辑结构却类似于同步代码。因此,我们仅需要进行一小部分编程的工作就可以获得异步编程的所有优点。

对于同步的代码,大家肯定都不陌生,因为我们平常写的代码大部分都是同步的,然而同步代码却存在一个很严重的问题,例如我们向一个Web服务器发出一个请求时,如果我们发出请求的代码是同步实现的话,这时候我们的应用程序就会处于等待状态,直到收回一个响应信息为止,然而在这个等待的状态,对于用户不能操作任何的UI界面以及也没有任何的消息,如果我们试图去操作界面时,此时我们就会看到”应用程序为响应”的信息(在应用程序的窗口旁),相信大家在平常使用桌面软件或者访问web的时候,肯定都遇到过这样类似的情况的,对于这个,大家肯定会觉得看上去非常不舒服。引起这个原因正是因为代码的实现是同步实现的,所以在没有得到一个响应消息之前,界面就成了一个”卡死”状态了,所以这对于用户来说肯定是不可接受的

2、优势

异步编程最大的优势其实就是提供系统执行效率,毕竟一个串行执行的程序不如并行来的快。譬如:一个人要干十件事情不如十个人各干一件事情效率高。

3、关键字

C# 中的async和await关键字都是异步编程的核心。通过使用这两个关键字,我们就可以在 .NET 轻松创建异步方法。

4、返回值类型

4.1、Void

如果在触发后,你懒得管,请使用 void。

void返回类型主要用在事件处理程序中,一种称为“fire and forget”(触发并忘记)的活动的方法。除了它之外,我们都应该尽可能是用Task,作为我们异步方法的返回值。

4.2、Task

你如果只是想知道执行的状态,而不需要一个具体的返回结果时,请使用Task。
与void对比呢,Task可以使用await进行等待新线程执行完毕。而void不需要等待。

4.3、Task<TResult>

当你添加async关键字后,需要返回一个将用于后续操作的对象,请使用Task<TResult>。

主要有两种方式获取结果值,一个是使用Result属性,一个是使用await。他们的区别在于:如果你使用的是Result,它带有阻塞性,即在任务完成之前进行访问读取它,当前处于活动状态的线程都会出现阻塞的情形,一直到结果值可用。所以,在绝大多数情况下,除非你有绝对的理由告诉自己,否则都应该使用await,而不是属性Result来读取结果值。

5、范例

再进行范例之前,先写一个错误的异步方法,如下:

        public static asyncTask SyncExec_3()
        {
            Proc();
        }

        public static voidProc()
        {
            for (int i = 0; i < 1000; i++)
            {
                Console.WriteLine(i);
            }
        }
View Code

asp.net core 系列 5 项目实战之:NetCore 的 async 和 await(参考自:Microsoft教程)第1张

由上图截图可以,在异步方法内,需要使用await关键字,否则方法会同步执行。

不是说你把一个方法标记成async这个方法就成了异步调用的方法了。async这个关键词其实反而是可以省略的,这个关键词存在的意义是为了向下兼容,为await提供上下文而已。

如下两个方法其实是一样的

        Task<int> DelayAndCalculate1(int a, intb)
        {
            return Task.Delay(1000).ContinueWith(t => a +b);
        }

        async Task<int> DelayAndCalculate2(int a, intb)
        {
            await Task.Delay(1000);
            return a +b;
        }
View Code

那么,既然async是可以省略的,那么await可以省略吗?答案是不可以,否则你的方法会被编译警告,会成为一个同步方法。

其实真正重要的是await,有没有async反而确实不重要。既然微软提供了这样的语法糖,所以建议大家在写异步方法是加上async。

下面我们通过实例来说明异步编程,如下:

5.1、返回值为Task的程序具体返回了什么?

        public static asyncTask SyncExec_2()
        {
            await Task.Run(() =>{
                 Proc();
            });
        }
View Code

通过调试,快速监视,得到如下消息:

asp.net core 系列 5 项目实战之:NetCore 的 async 和 await(参考自:Microsoft教程)第2张

其实返回值为Task的方法中什么也没返回,但是我们确定接收到他的返回值,这点似乎是个矛盾点。根据VS快速监视截图,我们发现我们接收的东西是一个上下文线程。

5.2、异步执行的顺序

核心代码:

asp.net core 系列 5 项目实战之:NetCore 的 async 和 await(参考自:Microsoft教程)第3张

我用短短几行实现了一个相对复杂的工作流,task直接的dependency很明确的表达在代码里,并且task1和task2可以并行执行,task3和task4可以并行执行。最后执行 doTask5。
详细代码如下:
usingSystem;
usingSystem.Diagnostics;
usingSystem.Threading.Tasks;

namespaceConsoleCore
{
    classProgram
    {
        static void Main(string[] args)
        {
            var Result =ComplexWorkFlow();
            Console.WriteLine("执行结束");
            Console.Read();
        }
        

     
        /// <summary>
        ///加法
        /// </summary>
        /// <param name="num1"></param>
        /// <param name="num2"></param>
        /// <returns></returns>
        public static int GetNum_Add(int num1, intnum2)
        {
            return num1+num2;
        }
        /// <summary>
        ///减法
        /// </summary>
        /// <param name="num1"></param>
        /// <param name="num2"></param>
        /// <returns></returns>
        public static int GetNum_Sub(int num1,intnum2)
        {
            return num1 -num2;
        }
        /// <summary>
        ///乘法
        /// </summary>
        /// <param name="num1"></param>
        /// <param name="num2"></param>
        /// <returns></returns>
        public static int GetNum_Mul(int num1,intnum2)
        {
            return num1 *num2;
        }

        /// <summary>
        ///除法
        /// </summary>
        /// <param name="num1"></param>
        /// <param name="num2"></param>
        /// <returns></returns>
        public static int GetNum_Cal(int num1, intnum2)
        {
            return num1 /num2;
        }



        /// <summary>
        ///10
        /// </summary>
        /// <returns></returns>
        public static async Task<int>DoTask1()
        {
            var result = 0;
            await Task.Run(() =>{
                result = GetNum_Add(5, 5);
            });
            Console.WriteLine("任务 DoTask1 执行完毕,结果为:" +result);
            returnresult;
        }

        /// <summary>
        ///90
        /// </summary>
        /// <returns></returns>
        public static async Task<int>DoTask2()
        {
            var result = 0;
            await Task.Run(() =>{
                result = GetNum_Sub(100,10);
            });
            Console.WriteLine("任务 DoTask2 执行完毕,结果为:" +result);
            returnresult;
        }

        /// <summary>
        ///100
        /// </summary>
        /// <param name="A"></param>
        /// <returns></returns>
        public static async  Task<int> DoTask3UseResultOfTask1(intA)
        {
            var result = 0;
            await Task.Run(() =>{
                result =GetNum_Mul(A, A);
            });
            Console.WriteLine("任务 DoTask3UseResultOfTask1 执行完毕,结果为:" +result);
            returnresult;
        }

        /// <summary>
        ///45
        /// </summary>
        /// <param name="A"></param>
        /// <returns></returns>
        public static async Task<int> DoTask4UseResultOfTask2(intA)
        {
            var result = 0;
            await Task.Run(() =>{
                result = GetNum_Cal(A, 2);
            });
            Console.WriteLine("任务 DoTask4UseResultOfTask2 执行完毕,结果为:" +result);
            returnresult;
        }

        /// <summary>
        ///两者平方和
        /// </summary>
        /// <param name="A"></param>
        /// <param name="B"></param>
        /// <returns></returns>
        public static async Task<int> DoTask5(int A, intB)
        {
            var result = 0;
            await Task.Run(() =>{
                result = A * A + B *B;
            });
            Console.WriteLine("最终的结果为:" +result);
            returnresult;
        }


        public static async Task<int>ComplexWorkFlow()
        {
            Task<int> task1 =DoTask1();
            Task<int> task2 =DoTask2();
            Task<int> task3 = DoTask3UseResultOfTask1(awaittask1);
            Task<int> task4 = DoTask4UseResultOfTask2(awaittask2);
            return await DoTask5(await task3, awaittask4);
        }
    }
}
View Code
运行上述代码,我们会发现:可能存在如下结果:
asp.net core 系列 5 项目实战之:NetCore 的 async 和 await(参考自:Microsoft教程)第4张

发现没有:执行结束反而先执行完毕了,呵呵呵,意不意外?

继续执行一次:

asp.net core 系列 5 项目实战之:NetCore 的 async 和 await(参考自:Microsoft教程)第5张

这次执行结束换到了第二行,呵呵,意不意外?

再执行一次:

asp.net core 系列 5 项目实战之:NetCore 的 async 和 await(参考自:Microsoft教程)第6张

额,(⊙o⊙)…又跑到第三行了,不执行了,大概说下吧。

task1和task2并行,task3和task4并行,但是task1肯定会在task3之后,Task2肯定会在task4之后,task3 和 task4 肯定会在Task5之后,至于输出的执行结束这段话,可能出现在任何位置。哈哈。就这么任性。

上述探讨了异步方法,但:

首先要知道async await解决了什么问题,不要为了异步而异步,针对高密集的cpu计算异步没太大意义,甚至可能有性能损耗。

其次说async await的实现,就以你的代码为例,如果没有async await的话代码执行步骤就不说了,在有async await后就不一样,一旦调用一个async方法,就是告知,这里我可能需要点时间来处理你先继续往后走吧(比如io操作),这块执行线程就会继续往后跑而不再关心async方法的返回直到看到对应的await后,就停下来等着await对应的task执行完(你async await的代码在编译后会变成一个状态机,这个你可以看下你这段代码在il中的实现),执行完后就会从对应的task展开(unwarp)拿到原始结果(比如你代码中几个await的地方),这里额外就可以回答你第Task和async await的差异,async await的表现是基于Task,但显式的Task会根据TaskScheduler启动线程,而async await不会额外新起线程,async await会从当前可用线程中找空闲的线程来执行,由于所有线程都没闲着(没有所谓的等待,特别是耗时的io等待),因此服务的吞吐量会高很多(适用于高io场景)

其实上面也解释了多线程和async await的差异了,多线程不等同于异步,你拿TaskFactory或者ThreadPool搞一堆线程,它们都做着同步的工作还是会在执行的时候阻塞,线程大量的时间就这样白白浪费在了等待响应上了。

参考文档:https://www.zhihu.com/question/58922017

https://q.cnblogs.com/q/99430/

      https://www.cnblogs.com/liqingwen/p/5831951.html

免责声明:文章转载自《asp.net core 系列 5 项目实战之:NetCore 的 async 和 await(参考自:Microsoft教程)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇MYSQLSpring Cloud Stream下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

记一个多线程使用libevent的问题

前段时间使用libevent网络库实现了一个游戏服务器引擎,在此记录下其中遇到的一个问题。 我在设计服务器上选择把逻辑和网络分线程,线程之间通信使用队列。但是这样做会有个问题: 当逻辑线程想要主动的发一个数据包的时候,网络线程此时可能还阻塞在等待网络IO的系统调用上(比如说epoll)。如果不做特殊处理的话,此时消息包就会一直积压在缓冲区中,直到下一次网络...

Java5 多线程实践

2006 年 1 月 18 日 Java5增加了新的类库并发集java.util.concurrent,该类库为并发程序提供了丰富的API多线程编程在Java 5中更加容易,灵活。本文通过一个网络服务器模型,来实践Java5的多线程编程,该模型中使用了Java5中的线程池,阻塞队列,可重入锁等,还实践了Callable, Future等接口,并使用了Jav...

Java并发编程-多线程

1、进程与线程   一个程序就是一个进程,一个程序中的多个任务被称为线程。进程是资源分配的基本单位,线程是进程中执行运算的最小单位,亦是调度运行的基本单位。多线程的好处并发执行提高了程序的效率,CPU不会因为某个线程需要等待资源而进入空闲状态 2、线程的实现方式 继承java.lang.Thread类 实现java.lang.Runnable接口,然后交...

关于Web服务器的认识

       马上就要毕业了,也要开始找工作了,大学写了这么多代码了,却没有好好总结一下常用的概念很是遗憾额,就通过这篇博客记录一下我最常用的一些知识好了。        说到Web服务器,有很多文章都介绍的很好,之前看到一篇非常不错的,对我帮助很大,可惜现在找不到原文了,看到博客园有人转载,我就在这里也记一下好了,在此非常感谢作者的分析,受益匪浅。   ...

【学习】026 Zookeeper

什么Zookeeper Zookeeper是一个分布式开源框架,提供了协调分布式应用的基本服务,它向外部应用暴露一组通用服务——分布式同步(Distributed Synchronization)、命名服务(Naming Service)、集群维护(Group Maintenance)等,简化分布式应用协调及其管理的难度,提供高性能的分布式服务。ZooKe...

java并发之线程执行器(Executor)

线程执行器和不使用线程执行器的对比(优缺点) 1.线程执行器分离了任务的创建和执行,通过使用执行器,只需要实现Runnable接口的对象,然后把这些对象发送给执行器即可。 2.使用线程池来提高程序的性能。当发送一个任务给执行器时,执行器会尝试使用线程池中的线程来执行这个任务。避免了不断创建和销毁线程导致的性能开销。 3.执行器可以处理实现了Callabl...