关于mvc5以及以上的controller中使用异步的讨论

圣殿骑士18 2018-05-01 09:49:47
在mvc5以及.net4.5下,看到异步会用的比较多。也看到csdn上有些大佬写的代码都会用到。对于vs来说,其创建的默认项目也是基于await和asyc的。比如这样的代码:

[ResponseType(typeof(MW_Goods))]
public async Task<IHttpActionResult> GetMW_Goods(string id)
{
MW_Goods mw_goods = await db.MW_Goods.FindAsync(id);
if (mw_goods == null)
{
return NotFound();
}

return Ok(mw_goods);
}

但对于我来说,我始终觉得,如果在没有必要用异步的地方使用异步并没有必要。所以我想搞清楚,到底哪些场景下,使用异步真的可以带来帮助?

我们先假设代码结构,代码简单实现为同步(或异步)的Controller + Service的结构,即控制器的一个方法,调用服务的一个方法完成一个请求。Controller 的转发是CPU操作,Service层的执行一般是数据库(IO)操作。这里引入了有IO的环节,也是一般情况下认为异步能够发挥作用的场景。我们就假设Controller层100%CPU运算,Service层100%IO运算。

我们再以web应用为例,讲讲最常用的场景。先看iis的机制,我们知道,对于iis服务器,在并发执行客户请求时,是有排队机制的。假设iis同一时刻能启动100个线程并发执行请求,而请求最大队列为1000,那么后900个请求将被压入队列。
假设1个请求执行完需要200ms,因为线程并发数是100,所以1000个请求执行完毕,就需要200 * (1000 / 100) = 2000ms。这是在服务端不做任何异步处理的情况下的结果。

那么异步能带来性能的提升吗?先引入两个假设:
这里要引入两个假设:
1、假设1:假设单个请求的200ms时间中,20ms用于路由转发,180ms用于服务执行,即1:9的时间比
2、假设2:假设Service方法内部没有实现异步的需求,这是大多数的情况,即一般一个service方法是一个整体事务,内部不存在可以并发执行的多个部分
再来看几种情况:
1、当前服务器不存在瓶颈。即用户并发请求数在100以下。此时请求总是立即被处理,不会排队,那么用户的请求执行始终还是200ms,性能没有提升。
2、当前服务器存在排队。假设持续的会有900个请求在排队,100个请求在执行。那么:
使用异步前:第1001个请求需要等待 200 * (1000 / 100) = 2000ms才会被执行,执行完成总时间是:2200ms
使用异步后:直觉上感觉应该是不能提高性能的。可以做个计算。
第1001个请求需要等待 20 * (1000 / 100) = 200ms 会被开始处理,但此时处理结果还未返回;那么Service层执行IO需要多少时间呢?假设一台服务器的单位时间的IO能力是固定的,那么IO能力计算为:100个线程并发需要执行180ms,那么理论上1个线程执行需要耗费IO资源1.8ms。那么第1001个线程开始执行需要等待的时间是,1.8 * 1000 = 1800ms,加上之前的200ms,刚好是2000ms,再加上单个线程执行的总时间,第1001个线程执行完,也需要2200ms。

依据以上分析,我们是不是可以得出结论,对于客户请求的响应性能:
1、简单的异步化改造,不管是服务器是否处于存在瓶颈的状态,都不能提高性能,反而因为异步增加了线程间切换的成本,性能还会有所下降。所谓简单的异步化改造,就是controller异步化 + service的单一化异步(即内部不包含多个IO相关的异步方法)
2、必须对sevice层的某个方法做内部多异步方法的改造,才有可能提升性能。而这要做到以及做好,并不容易。你需要:
(1)明确这个方法是允许做异步化改造的。但往往一个service方法,它不应该是异步的,即它很可能是前后相互依赖。依据我的经验,我的应用场景95%以上都是后面的代码要依赖前面的读取结果,无法异步化。
(2)异步化改造后,可能一个方法要被拆成多个方法,这给系统带来了复杂性。但这一点我还不确定,因为我没怎么实践过。可能Task类能解决这个问题,使一个方法内多段代码异步执行,但我想这对编程能力的要求,应该不低,普通程序员可能容易出错。

基于以上分析,我的疑问是:
如果异步真正能带来性能提升的只是所有业务场景中的5%,那还有必要将系统整体改造为异步吗?这带来的好处,和投入的精力,以及带来的复杂性,异步化线程切换的开销相比,是不是不值得了?
我个人更倾向于系统整体仍然保持同步的处理代码,然后根据系统的运行情况,只针对瓶颈真的存在的部分,进行分析和异步化,这是不是更好的方式?这既保持了系统架构的简洁性,开发的简单性,运维的稳定性,也能在异步化真正能带来效果的业务点上进行真正有效的异步化改造?

请多多指教。
...全文
1523 14 打赏 收藏 转发到动态 举报
写回复
用AI写文章
14 条回复
切换为时间正序
请发表友善的回复…
发表回复
masanaka 2018-05-02
  • 打赏
  • 举报
回复
引用 6 楼 daixf_csdn 的回复:
多谢p哥。我只考虑应用服务器层面的异步优化,没有考虑到整个操作系统层面上,异步所带来的好处,受益匪浅。 划一下重点: 1、一个好的系统,应该把 CPU 的能力几乎用尽 85%~95%。 2、异步编程正好是针对“后面的代码要依赖前面的读取结果”而言的。 3、所有的 大的服务程序都会倾向于用异步编程模式来改在代码,从本质上尽量使用尽操作系统、硬件的调度能力,而避免写轮询、阻塞、信号量式的代码。
  • 打赏
  • 举报
回复
微软强推异步task编程,而且貌似task与thread方式是有所不同的,貌似比thread方式性能更高,不过没去研究过真假,只是看过默认微软基于ThreadPool实现了TaskScheduler,所以如果task真性能优于Thread方式的话,那么将action定义成异步方式的确可以比action本身必定会是多线程方式有所性能提高 当然也可能并不会有性能提高,但基于业务逻辑之类的一般来说我们都会定义成task返回,那么直接在action返回task也是理所当然得事情,否则难不成再怪怪的写个wait?
sdfgrtyu 2018-05-02
  • 打赏
  • 举报
回复
asp.net中的异步,主要就是基于IO的异步,目的就是减少线程的使用,提高系统吞吐量和可伸缩性还有并发性,,,,,,。
维秀斯丢丢 2018-05-02
  • 打赏
  • 举报
回复
这是我上csdn见过最牛逼的帖子了。
圣殿骑士18 2018-05-01
  • 打赏
  • 举报
回复
再思考了下,还可以有一些推论: 1、如果在服务器存在瓶颈下,即使是方法做了多异步的改造,也不能提升性能,而只是优化了用户体验。即A方法通过多个操作异步,是把执行时间从2000ms降低到1000ms,但这也占用了更多的服务器IO资源,这会导致其他请求的响应从500ms上升到600ms。用户体验得到了优化。 2、很多将服务器异步化的帖子都讲到说,大大提升了并发数。在服务器不存在瓶颈的情况下,并发数是否能提升?我们前面假设到,一个同步请求执行时间是200ms,也就是每200ms会有一个请求被执行。而如果使用异步,20ms即有一个请求被执行,那么并发数会提高10倍。这确实是一个诱人的成果,当然前提是服务器不产生OI瓶颈。 而产生这种结果的原因,是因为IIS对并发线程数有限制,而且这个限制是多少我也没找到答案,有人说是30,50,服务器操作系统是不是多一点?我分析中假设的是100,这个有人知道么? 另外,还有个替代方案,就是web园,虽然iis不让自定义并发线程数,但还是提供了并发进程的实现方案。从总体上来说,异步改造比web园应该更节省资源一些。 分析到此,我之前的出的结论可能需要改一下,我最新的结论是: 1、对系统做整体的web化改造是有全面性的好处的,也就是说在服务器资源充足,不存在瓶颈的前提下,能够使iis同时处理更多的web请求。 2、基于第一点,对系统做简单的异步化改造,即可带来服务器服务能力的提升,而对复杂的5%的业务逻辑,有针对性的对service方法进行多并发的异步改造,可以改善这部分请求的用户体验。 3、如果不方便做异步改造(.net4.5之前),使用web园方案,可以达到类似的提高并发数的效果。 4、不管做不做异步化改造,单个请求的响应性能并不会提升
圣殿骑士18 2018-05-01
  • 打赏
  • 举报
回复
引用 7 楼 sp1234 的回复:
比如说 A 把钱给B,然后一门心思等着B把货给A;此时B把钱给了A然后一门心思等着A把另外一件货给B。可是此时假设两个人都是死等的方式,那么这就产生了死锁。 此时系统资源充足,但是却死锁了(至少了等了很长时间)。只要任何一个人稍微在这个细节上“异步处理”,在await前一个任务的结果时时也能处理下一个任务,整个死锁就打破了。
看来,异步还能盘活操作系统潜在没有发挥出来的性能,应该尽量去使用。
  • 打赏
  • 举报
回复
硬件资源条件有限,这就好像是“最大工作窗口有限”一样,在5个窗口下如果能调度50个人同时拿药,效率就比原来5个窗口同步地排队提高了几倍。
  • 打赏
  • 举报
回复
比如说 A 把钱给B,然后一门心思等着B把货给A;此时B把钱给了A然后一门心思等着A把另外一件货给B。可是此时假设两个人都是死等的方式,那么这就产生了死锁。 此时系统资源充足,但是却死锁了(至少了等了很长时间)。只要任何一个人稍微在这个细节上“异步处理”,在await前一个任务的结果时时也能处理下一个任务,整个死锁就打破了。
圣殿骑士18 2018-05-01
  • 打赏
  • 举报
回复
多谢p哥。我只考虑应用服务器层面的异步优化,没有考虑到整个操作系统层面上,异步所带来的好处,受益匪浅。 划一下重点: 1、一个好的系统,应该把 CPU 的能力几乎用尽 85%~95%。 2、异步编程正好是针对“后面的代码要依赖前面的读取结果”而言的。 3、所有的 大的服务程序都会倾向于用异步编程模式来改在代码,从本质上尽量使用尽操作系统、硬件的调度能力,而避免写轮询、阻塞、信号量式的代码。
sp1234_maJia 2018-05-01
  • 打赏
  • 举报
回复
“窗口”总是存在的。CPU并不可能真的同时执行几万、几千个线程任务,线程都是在那里排队的,每一个线程都要占用至少1兆空间,还有切换调度的时间。异步编程实际上就是最大限度地缩短了线程排队的策略。
  • 打赏
  • 举报
回复
异步编程正好是针对“后面的代码要依赖前面的读取结果”而言的,这类编程是应用 Task、async/await 之类的简洁语法,期望是更容易书写代码。所有的大的服务程序都会倾向于用异步编程模式来改在代码,从本质上尽量使用尽操作系统、硬件的调度能力,而避免写轮询、阻塞、信号量式的代码。
  • 打赏
  • 举报
回复
一个好的系统,应该把 CPU 的能力几乎用尽 85%~95%。不是让排在窗口前边的任务用“死轮询”而拖垮CPU,而是让这些任务去“一边儿等候”把CPU时间让给后边排队的任务,这就是异步。任务尽量细粒度、尽量并发,即使在只有2个核心的CPU上的一个进程最好也能有几百、上千个工作线程做着必要的(而非轮询阻塞)的业务,这就是异步的目的。
  • 打赏
  • 举报
回复 1
比如说一个大医院的药房有5个窗口,没一个窗口可能都排了十几人在那里等待拿药。一种方式是这些人排队、阻塞窗口,一个人把处方递到里边之后就死等在窗口;另一种方式是药房根据“里边的线程数”来动态依次收入多张药方,然后被收走药方的人就可以到一边去休息、等到大喇叭通知自己去另一个窗口取药了。这第二种方式,完全可能后送进去处方、但是先取到药品,因为自己的药比别人的更容易找!这就是异步:你不用阻塞、循环、死等着,别人会主动通知你来取结果的。 那么对于第一种方式,排在前边第一名的人肯定是觉得自己没有受到损失,反正自己守住窗口就霸占、卡死了药房里边的系统系统资源。而后一种形式,虽然拍在窗口第一名的人没有感觉提升了取药的效率,但是排在后边的人却感觉到药房里边提高了几十倍的效率。

62,047

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术交流专区
javascript云原生 企业社区
社区管理员
  • ASP.NET
  • .Net开发者社区
  • R小R
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

.NET 社区是一个围绕开源 .NET 的开放、热情、创新、包容的技术社区。社区致力于为广大 .NET 爱好者提供一个良好的知识共享、协同互助的 .NET 技术交流环境。我们尊重不同意见,支持健康理性的辩论和互动,反对歧视和攻击。

希望和大家一起共同营造一个活跃、友好的社区氛围。

试试用AI创作助手写篇文章吧