您的位置:首页 > 编程语言

实际案例:在现有代码中通过async/await实现并行

2016-11-15 20:03 537 查看
实际案例:在现有代码中通过async/await实现并行

一项新技术或者一个新特性,只有你用它解决实际问题后,才能真正体会到它的魅力,真正理解它。也期待大家能够多分享解一些解决实际问题的内容。

在我们遭遇“黑色30秒”问题的过程中,切身体会到了异步的巨大作用(详见从ASP.NET线程角度对“黑色30秒”问题的全新分析),于是开始逐步地用async/await改造现有代码。

今天早上在将一个MVC Controller中的Action改为异步的时候突然发现——其中有7个方法调用可以并行执行。

public async Task<ActionResult> BlogPostInfo(string blogApp, int blogId, int postId, Guid blogUserGuid)
{
//7个方法无关联的方法调用
}


如果通过async/await实现了这7个方法的并行,性能将会提高几倍,真是一个意外的惊喜!

惊喜之后,则要面对这样一个问题——如何以最低的成本实现?

这7个方法其他地方也在调用,不想直接把这些方法改为异步的;即使可以改为异步的,也不想一路改到底,最后在数据访问层调用ADO.NET的异步方法。

。。。

接着在园子里发现了另外一个惊喜——Jesse Liu的博文(async
& await 的前世今生)中的一张图片:



好帅的图!连执行顺序都标得清清楚楚。只要照着这张图,就可以轻松地用async/await实现并行。

需要注意的地方:

1)并行调用的目标方法必须是async的。

2)在并行期间,不能使用await。

以下是实现案例: 

下面的代码是需要并行执行的7个方法中的2个:

var tags = TagService.GetTag(blogId, postId);
if (!string.IsNullOrEmpty(tags))
{
info.Tags = string.Format("标签: {0}", TagService.GetTagLink(blogUrl, tags));
}

var categories = CategoryService.GetCateList(blogUrl, blogId, postId);
if (!string.IsNullOrEmpty(categories))
{
info.Categories = "分类: " + categories;
}


由于并行调用的目标方法必须是async的,并且我们不想修改原有的方法实现代码,所以我们增加2个async方法中转一下:

async方法1:

public static async Task<string> GetTagAsync(int blogId, int entryId)
{
return await Task.Run(() => { return GetTag(blogId, entryId); });
}


async方法2:

public static async string GetCateListAsnyc(string blogUrl, int blogId, int entryId)
{
return await Task.Run(() => { return GetCateList(blogUrl, blogId, entryId); });
}


然后在调用代码中,分别调用这2个async方法让其并行执行,之后再用await取执行结果。

var tagsTask = TagService.GetTagAsync(blogId, postId);
var categoriesTask = BlogCategoryService.GetCateListAsync(blogUrl, blogId, postId);

var tags = await tagsTask;
if (!string.IsNullOrEmpty(tags))
{
info.Tags = string.Format("标签: {0}", TagService.GetTagLink(null, blogUrl, tags));
}

var categories = await categoriesTask;
if (!string.IsNullOrEmpty((categories)))
{
info.Categories = "分类: " + categories;
}


真的很简单,很轻松! async/await果然好用!

标签:
C#, async

好文要顶
关注我
收藏该文







dudu
关注 - 301
粉丝 - 5718

+加关注

17
0

«
上一篇:困扰多日的C#调用Haskell问题竟然是Windows的一个坑
»
下一篇:微软的坑:Url重写竟然会引起IIS内核模式缓存不工作

posted @ 2014-05-08 11:10
dudu 阅读(13392) 评论(38)

编辑
收藏

评论列表

  
#1楼
2014-05-08 11:15
飞鸟_Asuka
 

学习了
支持(0)反对(0) http://pic.cnblogs.com/face/400008/20131104132630.png
  
#2楼
2014-05-08 11:25
eflay
 

方法本身没有IO异步省线程的话,效果就和tagsTask.Wait();差不多了(一个用当前线程,其余的用异步线程,然后用Wait()),也就没有多少省线程的目的了,同时也就不需要async await了,.net4.0下就是用Wait等待写类似的并发的。所以还是得先把所有底层数据库访问都用async await改造,也就是新写一个异步版慢慢改造,然后慢慢清除同步版的方法。
支持(1)反对(0)

  
#3楼
2014-05-08 11:29
alexander.bruce.lee
 

dudu亲自操刀啦!顶!!
支持(0)反对(0) http://pic.cnblogs.com/face/433928/20150805211518.png
  
#4楼
2014-05-08 11:54
埋头前进的码农
 

在Web中异步执行对性能到底起到什么作用呢?

异步执行难道能够提高IIS的吞吐量?
支持(0)反对(0) http://pic.cnblogs.com/face/273228/20140809085159.png
  
#5楼
2014-05-08 12:35
capad
 

做客户端的程序的时候为了不让UI线程阻塞,一般是这样用

var tagsTask = await TagService.GetTagAsync(blogId, postId);

var categoriesTask = await BlogCategoryService.GetCateListAsync(blogUrl, blogId, postId);

直接等待,两个方法是顺序执行的。

原来还可以这样用(两个业务逻辑不相关的方法)

var tagsTask = TagService.GetTagAsync(blogId, postId);

var categoriesTask = BlogCategoryService.GetCateListAsync(blogUrl, blogId, postId);

两个方法会同时执行,然后再等待

var tags = await tagsTask;

涨知识了~
支持(0)反对(0) http://pic.cnblogs.com/face/u260397.jpg?id=08201332
  
#6楼
2014-05-08 13:04
飞鸟_Asuka
 

@ clith
引用
做客户端的程序的时候为了不让UI线程阻塞,一般是这样用

var tagsTask = await TagService.GetTagAsync(blogId, postId);

var categoriesTask = await BlogCategoryService.GetCateListAsync(blogUrl, blogId, postId);

直接等待,两个方法是顺序执行的。

原来还可以这样用(两个业务逻辑不相关的方法)

var tagsTask = TagService.GetTagAsync(blogId, postId);

var categoriesTask = BlogC...

的确,我自己写了个demo,一开始用的是上面的方法,即直接在方法上用await,结果只是UI线程不阻塞而已,但是性能上和同步方法调用没有多大差别(甚至个别情况下还可能慢于同步调用)。但是用下面一种方法,即先执行方法再await,效果拔群,直接提升了几倍的效率
支持(0)反对(0) http://pic.cnblogs.com/face/400008/20131104132630.png
  
#7楼
2014-05-08 13:12
沈赟
 

@ 李飞麟
引用
在Web中异步执行对性能到底起到什么作用呢?

异步执行难道能够提高IIS的吞吐量?

同问
支持(0)反对(0) http://pic.cnblogs.com/face/u30988.jpg
  
#8楼
2014-05-08 13:23
非空
 

Task.Run(() => { return GetTag(blogId, entryId); }); 还是会消耗wokerthread,既然TagService.GetTagAsync已经是异步的了就可以直接Task.ContinueWhenAll(TagService.GetTagAsync, BlogCategoryService.GetCateListAsync...,这样就可以利用TagService.GetTagAsync本身的异步线程(也许是IOThread),也不用wait
支持(0)反对(0)

  
#9楼
2014-05-08 13:53
通用C#系统架构
 

很久没提高了,的确需要提高了
支持(1)反对(0) http://pic.cnblogs.com/face/u35584.jpg
  
#10楼
2014-05-08 14:07
MicroCoder
 

不解,await tagsTask 的时候难道不需要等待吗?和串行的有什么区别。。。。

和下面这样写的区别在哪里?

?
支持(0)反对(0) http://pic.cnblogs.com/face/u31305.png?id=25124834
  
#11楼
2014-05-08 14:12
lovebanyi
 

感觉不要用await.而直接用Task.run

最后面用Task.WaitAll 会更容易理解代码。

省的时间在于多个Task是并行的。 还有内部异步IO省下的时间。
支持(0)反对(0) http://pic.cnblogs.com/face/u9435.jpg?id=02111734
  
#12楼
2014-05-08 14:19
埋头前进的码农
 

@ 非空
引用
Task.Run(() => { return GetTag(blogId, entryId); }); 还是会消耗wokerthread,既然TagService.GetTagAsync已经是异步的了就可以直接Task.ContinueWhenAll(TagService.GetTagAsync, BlogCategoryService.GetCateListAsync...,这样就可以利用TagService.GetTagAsync本身的异步线程(也许是IOThread),也不用wait

他的GetTagAsync这个异步方式是自己实现的,是用

?
来实现的异步。
支持(0)反对(0) http://pic.cnblogs.com/face/273228/20140809085159.png
  
#13楼
2014-05-08 14:52
lovebanyi
 

await Task.WhenAll

代码这样写更好看。
http://www.asp.net/mvc/tutorials/mvc-4/using-asynchronous-methods-in-aspnet-mvc-4
支持(0)反对(0) http://pic.cnblogs.com/face/u9435.jpg?id=02111734
  
#14楼
2014-05-08 15:21
腾飞(Jesse)
 

@ 沈赟

每一个Request进来都会由一个IIS的线程来处理,有时候线程处理完一个请求之后可能不会直接释放,而是立马就去处理其他的请求,这样可以减少创建线程的开销,但是这些线程是有IIS进程池来统一管理和分配的。

进程池里面的线程是有一个最大数量的限制(可以通过IIS来设置),比如说10,那么最多就只能有10个处理线程来处理请求。 这里的async 和await 的优点在于,await 后面有新的线程来处理IO任务,读数据库,文件,或者远程什么的。 (这个新创建的线程不是进程池里面那种用来处理请求的线程),在这种情况下,我们的主线程不会卡在这里,而是会回到线程池中,可以用来处理其他的请求。

所有我们10个处理线程就可以处理超过10个以上的线程。

有人可能会问,await 后面那个任务处理完了怎么办? 如何回到原来的线程?

答案是不用回到原来的线程,线程池会再找一个空闲的线程来处理, 所有有时候,同一个请求会被多个不同的处理线程处理。
支持(3)反对(1) http://pic.cnblogs.com/face/554526/20131126113744.png
  
#15楼
2014-05-08 15:35
飞鸟_Asuka
 

@ lovebanyi
引用
await Task.WhenAll

代码这样写更好看。
http://www.asp.net/mvc/tutorials/mvc-4/using-asynchronous-methods-in-aspnet-mvc-4
这只是代码上比较简洁,性能上没啥大差异
支持(0)反对(0) http://pic.cnblogs.com/face/400008/20131104132630.png
  
#16楼
2014-05-08 16:28
*深海
 

@dudu,性能提高了多少,有测试一下哇?
支持(0)反对(0) http://pic.cnblogs.com/face/u10458.jpg?id=11092332
  
#17楼[楼主]
2014-05-08 17:19
dudu
 

@ 李飞麟

提高吞吐量是主要好处,而且减少了请求排队,排队直接影响请求的响应速度。
支持(0)反对(0) http://pic.cnblogs.com/face/1/20150530204900.png
  
#18楼[楼主]
2014-05-08 17:22
dudu
 

@ *深海

不太好直接测试比较,即使是同一段同步代码,在不同的负载场景下,执行时间有时相差也会很大。
支持(0)反对(0) http://pic.cnblogs.com/face/1/20150530204900.png
  
#19楼
2014-05-09 08:34
519740105
 

发现async/await的异步存在一个很大的不现实:

异步线程与主线程交互。

当我们要在线程里对UI进行操作(如桌面应用)的时候,通常都要通过反调用主线程来实现,但是,因为使用await阻塞了主线程,此时,调用主线程的子线程也会被阻塞。

这个问题,各位有什么方案?目前我还是使用老办法启动线程来达到目的。
支持(0)反对(0)

  
#20楼
2014-05-09 09:59
埋头前进的码农
 

@ 519740105
引用
发现async/await的异步存在一个很大的不现实:

异步线程与主线程交互。

当我们要在线程里对UI进行操作(如桌面应用)的时候,通常都要通过反调用主线程来实现,但是,因为使用await阻塞了主线程,此时,调用主线程的子线程也会被阻塞。

这个问题,各位有什么方案?目前我还是使用老办法启动线程来达到目的。

这个异步使用的主要场景应该是提高程序的处理性能。而不是winform程序中防止UI线程堵塞吧。
支持(0)反对(0) http://pic.cnblogs.com/face/273228/20140809085159.png
  
#21楼
2014-05-09 10:09
519740105
 

@ 李飞麟
引用
@519740105
引用引用发现async/await的异步存在一个很大的不现实:

异步线程与主线程交互。

当我们要在线程里对UI进行操作(如桌面应用)的时候,通常都要通过反调用主线程来实现,但是,因为使用await阻塞了主线程,此时,调用主线程的子线程也会被阻塞。

这个问题,各位有什么方案?目前我还是使用老办法启动线程来达到目的。

这个异步使用的主要场景应该是提高程序的处理性能。而不是winform程序中防止UI线程堵塞吧。

一个语法糖,简化多线程调用而已,同时使得代码优雅、简洁、易懂。但跟我们自己手写带来来实现多线程来实现的功能相比,就有这样的不足了。

比如,下载,这个是很常见的一个功能,我们在下载中肯定需要对下载进度反馈给UI,让用户能看到,如果使用async/await来实现,就达不到目的。我之前在实现一个简单的邮件发送功能的时候就遇到了这个问题。
支持(0)反对(0)

  
#22楼
2014-05-09 10:13
埋头前进的码农
 

@ 519740105

dudu说的很明白了。在web开发中,异步可以最大化IIS的吞吐量。

在winform中,没用过。不知道多线程在winform中有没有线程同步和线程堵塞的问题?
支持(0)反对(0) http://pic.cnblogs.com/face/273228/20140809085159.png
  
#23楼
2014-05-09 10:15
519740105
 

@ 李飞麟

不牵涉到类似WINFORM的UI操作的问题,自然是OK的。
支持(0)反对(0)

  
#24楼
2014-05-09 12:11
519740105
 

@ 李飞麟

问题解决了。那时,我是在4.0版本,现在用4.5版本,使用Task.Run完美解决。
支持(0)反对(0)

  
#25楼
2014-05-11 23:30
Sword-Breaker
 

如果只是单纯把同步的代码用Task包装的话,实际上提升有限,在使用真正的异步IO的情况下性能才有真正巨大的提升...我之前做一个warp后端API的东西的时候就亲自体验过。后端的API不支持批量,我开始在前端用Task去包装同步的HttpClient请求,CPU消耗高,QPS提升不多,后来调用GetStringAsync之后性能提升非常明显
支持(1)反对(0)

  
#26楼
2014-07-06 03:09
青楼满座
 

这种重构需要非常的小心吧,很容易引入潜在的同步冲突
支持(0)反对(0) http://pic.cnblogs.com/face/575116/20140709131412.png
  
#27楼
2015-02-10 16:04
wsion
 

t
支持(0)反对(0)

  
#28楼
2015-02-11 17:06
杨恒连
 

<img alt="" src="http://images.cnitblog.com/blog/523928/201502/111705575263520.png" border="0" "="">

这个返回的是string,为什么还需要await呢?
支持(0)反对(0) http://pic.cnblogs.com/face/523928/20160607103029.png
  
#29楼
2015-05-12 21:17
BloodyAngel
 

@ MicroCoder
引用
不解,await tagsTask 的时候难道不需要等待吗?和串行的有什么区别。。。。

和下面这样写的区别在哪里?

你这样是使用 UI 线程直接等待结果。使用 await 后,是服务执行完成后,再回调 UI 线程执行后续代码。
支持(0)反对(0) http://pic.cnblogs.com/face/u33907.bmp
  
#30楼
2015-05-12 21:20
BloodyAngel
 

@ Sword-Breaker
引用
如果只是单纯把同步的代码用Task包装的话,实际上提升有限,在使用真正的异步IO的情况下性能才有真正巨大的提升...我之前做一个warp后端API的东西的时候就亲自体验过。后端的API不支持批量,我开始在前端用Task去包装同步的HttpClient请求,CPU消耗高,QPS提升不多,后来调用GetStringAsync之后性能提升非常明显

真正的异步IO方法,内部实现不是直接使用线程池,效率更高。
支持(0)反对(0) http://pic.cnblogs.com/face/u33907.bmp
  
#31楼
2015-05-28 23:02
susilent
 

@ 我和小菜

这个应该是是作者的"笔误",用async修饰的方法返回值必须是void,Task或者Task<T>
支持(0)反对(0) http://pic.cnblogs.com/face/241342/20130301113909.png
  
#32楼
2015-08-11 14:01
学亮
 

请问下楼主用了_dbContext.SaveChangesAsync();方法了吗,就是EF6的异步操作方法?我用的时候报这个错A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous
operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.不知道怎么解决
支持(0)反对(0) http://pic.cnblogs.com/face/345474/20150120071040.png
  
#33楼[楼主]
2015-08-11 18:59
dudu
 

@ 学亮

没遇到过这个问题,建议你在博问( http://q.cnblogs.com/ )中问一下。
支持(0)反对(0) http://pic.cnblogs.com/face/1/20150530204900.png
  
#34楼
2015-10-30 17:11
njl_041x
 

可以说是一个多线程优化的经典案例,很实用!
支持(0)反对(0) http://pic.cnblogs.com/face/240878/20151016115127.png
  
#35楼
2015-12-02 10:37
Roy_se7en
 

@ 腾飞(Jesse)
引用
@沈赟

每一个Request进来都会由一个IIS的线程来处理,有时候线程处理完一个请求之后可能不会直接释放,而是立马就去处理其他的请求,这样可以减少创建线程的开销,但是这些线程是有IIS进程池来统一管理和分配的。

进程池里面的线程是有一个最大数量的限制(可以通过IIS来设置),比如说10,那么最多就只能有10个处理线程来处理请求。 这里的async 和await 的优点在于,await 后面有新的线程来处理IO任务,读数据库,文件,或者远程什么的。 (这个新创建的线程不是进程池里面那种用来处理请求的线程),在这种情况下,我们的主线程不会卡在这里,而是会回到线程池中,可以用来处理其他的请求...

如果是同一个请求会被不同的线程来处理,那把异步处理的结果响应给客户端是怎么做的?
支持(0)反对(0)

  
#36楼
2016-02-18 17:39
shenzhigang
 

异步后 ,有无解决你们的黑色30秒问题?
支持(0)反对(0) http://pic.cnblogs.com/face/344381/20151203203539.png
  
#37楼[楼主]
2016-02-18 17:43
dudu
 

@ shenzhigang

最后演变为黑色1秒问题,至今未解决。
支持(0)反对(0) http://pic.cnblogs.com/face/1/20150530204900.png
  
#38楼34377652016/5/24
0:04:57 2016-05-24 00:04
杨义金
 

mark
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: