C#并发编程之异步编程(二)
写在前面
前面一篇文章介绍了异步编程的基本内容,同时也简要说明了async和await的一些用法。本篇文章将对async和await这两个关键字进行深入探讨,研究其中的运行机制,实现编码效率与运行效率的提升。
异步方法描述:使用async修饰符来标识一个方法或Lambda表达式的,被称之为异步方法。
异步方法编译:编译器在遇到await表达式后会截断方法,并将剩余的异步方法注册为在等待任务完成后需要继续执行的后续部分。
异步方法基础及其运行流程
Async和Await
异步方法使用async修饰,该方法包含一个或多个await表达式或语句,方法同步运行,直至到达第一个 Await
,此时暂停,直到等待的任务完成,在任务完成后,控制权返回给方法的调用方。如果方法中并不包含await,则该方法不会像同步方法一样被挂起。
异步方法通常包含await运算符的一个或多个实例,但缺少await表达式也不会导致生成编译器错误,之会因为没有await而发出警告,但编译依然通过。
异步方法使用await关键字来确定等待位置,但await表达式并不阻止正在执行到此位置的线程,也就是说异步方法在await表达式执行时只是暂停,并不会导致方法退出,只会导致finally代码块不运行。异步方法只有在等待的任务完成后,才能通过该位置并继续执行剩下的逻辑,控制权也在此处返回给异步方法的调用方。
如果异步方法未使用Await运算符标记暂停点,那么异步方法会作为同步方法执行,即使有Async修饰符,也不例外。如以下示例
1: public async static Task<string> GetUserInfoAsync()
2: {
3: User user = await db.User.FirstOrDefaultAsync();//此处会挂起
4:
5: Task<User> user = db.User.FirstOrDefaultAsync();//此处不会挂起,注意此处,返回值也变了,接下来会讨论一下异步方法的返回值
6:
7: return string.Empty;
8: }
具MSDN描述,aysnc关键字是一个非保留的关键字。在修饰方法或 lambda 表达式时,它是关键字,await也作为关键字存在。在所有其他上下文中,async和await都会将其解释为标识符。不过开发人员可以不用太过关注这段,只需要知道aysnc会将一个方法标识成异步方法,而await可以挂起异步方法的执行即可。
关键点
1、和被async修饰的方法不一样,如果方法中含有await关键字,方法必须使用async标识符,否则编译不通过。
2、在异步编程过程中,比较推荐的做法是,被标记了async关键字的异步方法应该包含至少一个await表达式或语句。
3、异步方法的命名以Async结尾
异步返回类型和异常处理
需要说明的是,本文所讨论的异步方法指的是基于任务的异步编程模型,返回值是,Task或Task<TResult>。
1、如果方法需要返回string类型,那么将返回Task<string>。如果方法没有指定返回类型,那么将返回Task。每个返回的任务都表示正在进行的工作,任务封装有关异步进程状态的信息,如果未成功,则会引发异常。异步方法返回 Task 或 Task<TResult>。返回任务的属性携带有关其状态和历史记录的信息,如任务是否完成、异步方法是否导致异常或已取消以及最终结果是什么。可使用await运算符访问这些属性。
1: public async static Task<User> GetUserInfoAsync()
2: {
3: User user = await db.User.FirstOrDefautAsync();
4:
5: return user;
6: }
2、如果等待的任务返回异步方法导致异常,则 await 运算符会以同步方式抛出异常。如果等待的返回任务的异步方法取消,await运算符引发OperationCanceledException。如果异步方法中没有使用await阻塞,可以使用try-catch捕捉异常,只是异常发生的时机可能会滞后。
异步方法的运行流程
了解异步方法的运行机制,就是要了解异步编程中的控制流是如何一步步执行的。如果需要详细了解控制流,可以异步到MSDN中查看。
下图及其描述摘自MSDN:
关系图中的数值对应于以下步骤。
事件处理程序调用并等待
AccessTheWebAsync
异步方法。
AccessTheWebAsync
创建HttpClient实例并调用GetStringAsync异步方法,获取的内容字符串方式返回。
GetStringAsync
中发生了某种情况,该情况挂起了它的进程。可能必须等待其他阻止任务完成。为避免阻止资源,GetStringAsync
会将控制权出让给其调用方AccessTheWebAsync
。GetStringAsync
返回Task<TResult>,其中 TResult 为字符串,并且AccessTheWebAsync
将任务分配给getStringTask
变量。该任务将调用GetStringAsync
正在进行的进程,在调用完成时产生返回字符串给urlcontent。由于尚未等待
getStringTask
,因此,AccessTheWebAsync
可以继续执行而不依赖于GetStringAsync
最终结果的完成。该任务继续调用同步方法DoIndependentWork
。
DoIndependentWork
作为一个同步方法,在自身工作完成后返回到调用方。
AccessTheWebAsync
已运行完毕,可以不受getStringTask
的结果影响。接下来,AccessTheWebAsync
需要计算并返回已下载的字符串的长度,但该方法只有在获得字符串的情况下才能计算该值。因此,
AccessTheWebAsync
使用一个 await 运算符来挂起其任务,并把控制权交给调用AccessTheWebAsync
的事件处理程序。AccessTheWebAsync
将Task<int>
返回给调用方。该任务将计算下载字符串长度。
GetStringAsync
完成并生成一个字符串结果。字符串结果不是通过按你预期的方式调用GetStringAsync
所返回的。(记住,该方法已返回步骤 3 中的一个任务)。相反,字符串结果存储在表示getStringTask
方法完成的任务中。await 运算符从getStringTask
中检索结果。赋值语句将检索到的结果赋给urlContents
。当
AccessTheWebAsync
获取字符串结果时,该方法可以计算字符串长度。然后,AccessTheWebAsync
工作也将完成,并且等待事件处理程序的继续使用。事件处理程序也将最终获得字符串的长度信息。注意:
如果 GetStringAsync(因此 getStringTask)在 AccessTheWebAsync 等待前完成,则控制权会保留在 AccessTheWebAsync中。如果异步调用过程 (AccessTheWebAsync) 已完成,并且 AccessTheWebSync 不必等待最终结果,则挂起然后返回到 getStringTask 将造成资源浪费。
在调用方内部(此示例中的事件处理程序),处理模式将继续。在等待结果前,调用方可以开展不依赖于 AccessTheWebAsync 结果的其他工作,否则就需等待片刻。事件处理程序等待 AccessTheWebAsync,而 AccessTheWebAsync 等待 GetStringAsync。
异步编程对性能的影响
在.NET异步编程中,async和await不会创建其他线程,同时异步方法不会在其自身线程上运行,因此它不需要多线程。只有当方法处于活动状态时,该方法将在当前同步上下文中运行并使用线程上的时间。可以使用Task.Run将占用大量CPU的工作移到后台线程,但是后台线程不会帮助正在等待结果的进程变为可用状态。
对于异步编程而言,基于异步的方法优于几乎每个用例中的现有方法。具体而言,这种方法优于BackgroundWorker的I/O绑定操作因为代码更简单且无需防止争用条件。结合Task.Run使用时,异步编程比BackgroundWorker更适用于CPU绑定的操作,因为异步编程将运行代码的协调细节与Task.Run传输至线程池的工作区分开来。
那么异步编程对线程的影响又是什么呢,相比大家应该都知道,ASP.NET中有两类线程,工作线程,和IO线程。
其中工作线程处理普通请求的线程,也是我们用得最多的线程。这个线程是有限的,是根CPU的个数相关的。IO线程,比如与文件读写,网络操作等是可以异步实现并且使性能提升的地方。I/O线程通常情况下是空闲的。所以可以使用IO线程来代替工作线程,一方面充分运用了系统资源,另一方面也节省了工作线程调度及切换所带来的损耗。
由此我们需要明白,在I/O密集型处理时,使用异步可以带来很大的提升,比如数据库操作以及网络操作。
即便异步编程带来性能的提升,但是运用不慎,也会对系统性能产生反作用,比如直接使用Task.Run或者Task.Factory.StartNew所带来的异步编程,这些方式会占用工作线程以及工作线程之间的切换。
异步编程需要注意的地方
1、同时async和await侵入性或者传递性很强,所有调用的地方都需要同步使用async和await,这对系统中老代码的修改产生了很大的影响。
2、异步编程中无法使用lock锁,因为异步方法不会在自身线程上运行,lock就变成了多余的了。但异步编程场景下可以使用AsyncLock锁,对相应的代码进行锁定。
3、异步编程里,比较推荐的做法是避免上线文延续,此处不再做更多说明,参考我的前一篇文章《异步编程(一)》
4、异步编程是否真的提升了系统性能,目前来看大多数场景下是提升了,尤其在I/O操作比较密集的业务场景下,比如查询数据库和网络调用。
C#并发编程之异步编程(二)相关推荐
- C#并发编程之异步编程(一)
写在前面 C#5.0中,对异步编程进行了一次革命性的重构,引入了async和await这两个关键字,使得开发人员在不需要深刻了解异步编程的底层原理,就可以写出十分优美而又代码量极少的代码.如果使用得当 ...
- 异步编程之异步编程与多线程编程的联系和区别
1.异步编程与多线程的区别 共同点:异步和多线程两者都可以达到避免调用线程阻塞的目的,从而提高软件的可响应性 不同点: (1)线程不是一个计算机硬件的功能,而是操作系统提供的一种逻辑功能,线程本质上是 ...
- JS笔记(20): JS中的同步编程和异步编程
铺垫:关于定时器 定时器:设定一个定时器,并且设定了等到的时间,当到达指定的时间,浏览器会把对应的方法执行 1)常用的定时器 1.setTimeout(function,intarval) 执行一次 ...
- C/C++编程:异步编程入门
背景 在产品端开发软件多了,免不了遇到多线程处理的情况,这也符合多核.异构的现代化硬件发展的需求.多线程处理中常见的有两种应用情形: 并行化算法处理.利用OpenMP/TBB等CPU并行库,或者CUD ...
- 响应式编程(反应式编程)的来龙去脉(同步编程、多线程编程、异步编程再到响应式编程)
响应式编程的来龙去脉(同步编程.多线程编程.异步编程再到响应式编程) 文章目录 响应式编程的来龙去脉(同步编程.多线程编程.异步编程再到响应式编程) 简介 1. 示例 2. 同步编程 3. 多线程编程 ...
- C#并发编程之异步编程(三)
写在前面 本篇是异步编程系列的第三篇,本来计划第三篇的内容是介绍异步编程中常用的几个方法,但是前两篇写出来后,身边的朋友总是会有其他问题,所以决定再续写一篇,作为异步编程(一)和异步编程(二)的补充. ...
- python2异步编程_Python 异步编程入门
本文是写给 JavaScript 程序员的 Python 教程. Python 的异步编程,其他人可能觉得很难,但是 JavaScript 程序员应该特别容易理解,因为两者的概念和语法类似.JavaS ...
- python2异步编程_python异步编程入门
这几天看代码,总是会接触到很多异步编程,之前只想着实现功能,从来没考虑过代码的运行快慢问题,故学习一番. 从0到1,了解python异步编程的演进 1.urllib与requests爬虫 reques ...
- python2异步编程_python异步编程 (转载)
转自:https://zhuanlan.zhihu.com/p/27258289 本文将会讲述Python 3.5之后出现的async/await的使用方法,以及它们的一些使用目的,如果错误,欢迎指正 ...
最新文章
- token 的设计方案
- Android 2D Graphics
- 回到intellij的初始界面
- HDU 3072 SCC Intelligence System
- 抓取AJAX网页的方法-Firefox组件,C#集成
- 联想小新模式切换不了_小新Pro 14 2021正式发布,联想举行秋季新品发布会,10款新品齐亮相...
- 对linux的CFS调度算法的理解
- 切断数据感知控件,大大提升软件运行速度
- 台式计算机键盘驱动,提示检测到不兼容的键盘驱动程序怎么解决 教你几个步骤轻松修复...
- 文件服务器phpstudy,使用phpstudy搭建ftp服务器
- 【deepstream部署Yolov6】
- 第二次作业—时事点评
- 微信小程序注册流程图文详解
- Lync2013 升级Skype For Bussiness 2015 升级思路整理
- windows一键睡眠,不是休眠!
- 汇编实现通过输入被加数与加数,实现加法
- 数据转移高效方法(oracle)
- 医号馆基层医疗信息化管理软件升级更新!
- 程序员求职之道(《程序员面试笔试宝典》)之自己的强项或是研究方向与中意的工作岗位不一致怎么办?...
- 干饭了干饭了!Java8种排序算法下饭总结