走进异步世界-犯傻也值得分享:ConfigureAwait(false)使用经验分享
在上周解决“博客程序异步化改造之后遭遇的性能问题”的过程中,我们干了一件自以为很有成就感的事——在表现层(MVC与WebForms)将所有使用await的地方都加上了ConfigureAwait(false),比如下面代码:
var taskCategories = GetCategoriesAsync();
model.Posts = await GetPostsAsync(model).ConfigureAwait(false);
model.Paging.TotalCount = await taskTotalCount.ConfigureAwait(false);
model.HeadlineHtml = await taskHeadlineHtml.ConfigureAwait(false);
model.Categories = await taskCategories.ConfigureAwait(false);
干完之后才恍然大悟,我们“出色”地完成了一件傻事,性能问题并没有得到解决,最终发现问题的真正原因是我们修改的EnyimMemcaced代码存在内存泄漏问题。
犯傻之后,郁闷之时,意外地发现竟然有一些收获可以分享,于是自伤的心有了些许安慰。
在干这次傻事之前,我们分不清默认的ConfigureAwait(continueOnCapturedContext:true)与ConfigureAwait(false)的区别,只知道一个是在异步执行时捕获上下文,一个是在异步执行时不捕获上下文。更不知道ConfigureAwait(false)会带来什么影响?
傻过之后,我们对此多了一点了解:
1)当ConfigureAwait(true),代码由同步执行进入异步执行时,当前同步执行的线程上下文信息(比如HttpConext.Current,Thread.CurrentThread.CurrentCulture)就会被捕获并保存至SynchronizationContext中,供异步执行中使用,并且供异步执行完成之后(await之后的代码)的同步执行中使用(虽然await之后是同步执行的,但是发生了线程切换,会在另外一个线程中执行「ASP.NET场景」)。这个捕获当然是有代价的,当时我们误以为性能问题是这个地方的开销引起,但实际上这个开销很小,在我们的应用场景不至于会带来性能问题。
2)当Configurewait(flase),则不进行线程上下文信息的捕获,async方法中与await之后的代码执行时就无法获取await之前的线程的上下文信息,在ASP.NET中最直接的影响就是HttpConext.Current的值为null。
我们在犯傻过程中,工作量最大的就是处理HttpConext.Current为null的情况。
由于之前写代码时的幼稚与偷懒,造成了很多地方用HttpConext.Current去获取http请求相关信息。在异步化改造之后,HttpConext.Current也遍布在很多aync方法中。Configurewait(flase)之后,NullReferenceException如雨后春笋。
针对这样的窘境,我们只能一个个修改代码,通过方法参数传递所需要的HttpConext信息,取代原先的HttpConext.Current“绿色通道”访问方式。
还有些地方根本不需要HttpConext.Current,只是因为当初的幼稚,比如Server.MapPath(),Server.UrlEncode(),进行了这样的更改:
System.Web.Hosting.HostingEnvironment.MapPath();
HttpUtility.UrlEncode();
在处理HttpConext.Current为null的情况中,我们遇到了一个棘手的问题,它出现在MVC与WebForms混用的场景——在MVC Controller中加载WebForms中的UserControl(也就是让UserControl直接Render为字符串)。
之前我们在MVC中是这样处理的:
page.Controls.Add(commentControl);
using (var sw = new StringWriter())
{
HttpContext.Current.Server.Execute(page, sw, true);
commentsHtml = sw.ToString();
}
可是现在HttpContext.Current为null,不得不改成这样:
page.Controls.Add(commentControl);
var sb = new StringBuilder();
using (var sw = new StringWriter(sb))
{
using (var htw = new HtmlTextWriter(sw))
{
commentControl.RenderControl(htw);
commentsHtml = sb.ToString();
}
}
改好之后发现,只要.ascx中用到了HyperLink控件并访问NavigateUrl属性,就会出现NullReferenceException。原来HyperLink.NavigateUrl中调用了ResolveClientUrl方法,而ResolveClientUrl时会访问Context.Request.ClientBaseDir.VirtualPathString,而Context为null。
在这个地方折腾了不少时间,后来绕道解决了这个问题,用Attributes.Add取代HyperLink.NavigateUrl,比如:
hl.Attributes.Add("href", "http://www.cnblogs.com/");
在WebForms的UserControl中添加ConfigureAwait(false)时,我们开始时以为await之后的代码如果访问Context,也会引发NullReferenceException,而事实表明不会引发。因为在ASP.NET实例化UserControl时会将HttpContext的值传给UserControl的Context属性,所以在UserControl无需通过线程上文获取HttpContext信息(Page的情况也一样)。
另外一个受影响的地方就是线程的CurrentCulture设置,之前我们是在Application_BeginRequest中处理的,代码如下:
protected void Application_BeginRequest(Object sender, EventArgs e)
{
CultureInfo newci = new CultureInfo("zh-CN");
newci.DateTimeFormat.DayNames = new string[] { "日", "一", "二", "三", "四", "五", "六" };
newci.DateTimeFormat.FirstDayOfWeek = DayOfWeek.Sunday;
System.Threading.Thread.CurrentThread.CurrentCulture = newci;
}
但是由于ConfigureAwait(false),异步执行中的线程切换会造成CurrentCulture丢失。
解决方法很简单,直接设置所有线程的CurrentCulture,代码如下:
protected void Application_Start(Object sender, EventArgs e)
{
var newCulture = new CultureInfo("zh-CN");
newCulture.DateTimeFormat.DayNames = new string[] { "日", "一", "二", "三", "四", "五", "六" };
newCulture.DateTimeFormat.FirstDayOfWeek = DayOfWeek.Sunday;
CultureInfo.DefaultThreadCurrentCulture = newCulture;
}
ConfigureAwait(false)的使用经验值得分享的就这些。
在这次异步化改造过程中,不仅加ConfigureAwait(false)是干傻事,整个异步化改造就是一件大傻事。改造过程很艰辛,多次犹豫是不是要这么彻底地异步化,最终还是坚持了下来。当解决了内存泄漏问题之后,如释重负!异步化改造效果明显,超出了预期——响应性能与吞吐能力都得到了提升。过程中所有的煎熬与痛苦都被成功后涌上心头的那种兴奋所秒杀。
【参考资料】
Best practice to call ConfigureAwait for all server-side code
It's All About the SynchronizationContext
走进异步世界-犯傻也值得分享:ConfigureAwait(false)使用经验分享的更多相关文章
- 走进异步世界:EnyimMemcached异步化改造引起的内存泄漏
6月30日我们发布了异步化改造后的博客程序之后,出现了高内存.高CPU.高线程数的不理想情况. 经过一周的追查,终于水落日出——引起不理想情况的根源是我们修改过的EnyimMemcached代码存在内 ...
- [C#] 走进异步编程的世界 - 开始接触 async/await
走进异步编程的世界 - 开始接触 async/await 序 这是学习异步编程的入门篇. 涉及 C# 5.0 引入的 async/await,但在控制台输出示例时经常会采用 C# 6.0 的 $&qu ...
- [C#] 走进异步编程的世界 - 剖析异步方法(上)
走进异步编程的世界 - 剖析异步方法(上) 序 这是上篇<走进异步编程的世界 - 开始接触 async/await 异步编程>(入门)的第二章内容,主要是与大家共同深入探讨下异步方法. 本 ...
- [C#] 走进异步编程的世界 - 剖析异步方法(下)
走进异步编程的世界 - 剖析异步方法(下) 序 感谢大家的支持,这是昨天发布<走进异步编程的世界 - 剖析异步方法(上)>的补充篇. 目录 异常处理 在调用方法中同步等待任务 在异步方法中 ...
- [C#] 走进异步编程的世界 - 在 GUI 中执行异步操作
走进异步编程的世界 - 在 GUI 中执行异步操作 [博主]反骨仔 [原文地址]http://www.cnblogs.com/liqingwen/p/5877042.html 序 这是继<开始接 ...
- 走进异步编程的世界 - 开始接触 async/await
[C#] 走进异步编程的世界 - 开始接触 async/await 走进异步编程的世界 - 开始接触 async/await 序 这是学习异步编程的入门篇. 涉及 C# 5.0 引入的 async ...
- [C#] 走进异步编程的世界 - 开始接触 async/await(转)
原文链接:http://www.cnblogs.com/liqingwen/p/5831951.html 走进异步编程的世界 - 开始接触 async/await 序 这是学习异步编程的入门篇. 涉及 ...
- 走进异步编程的世界 - 开始接触 async/await(转)
序 这是学习异步编程的入门篇. 涉及 C# 5.0 引入的 async/await,但在控制台输出示例时经常会采用 C# 6.0 的 $"" 来拼接字符串,相当于string.Fo ...
- 走进异步编程的世界--async/await项目使用实战
起因:今天要做一个定时器任务:五分钟查询一次数据库发现超时未支付的订单数据将其状态改为已经关闭(数据量大约100条的情况) 开始未使用异步: public void SelfCloseGpPayOrd ...
随机推荐
- 我所理解的OOP——UML六种关系
最近由于经常给公司的小伙伴儿们讲一些OOP的基本东西,每次草纸都被我弄的很尴尬,画来画去自己都乱了,有时候也会尝试使用UML表示类之间的关系,但UML从毕业后就再也没接触过了,经常会被小伙伴儿们指出继 ...
- Qt 设置应用程序图标
Qt4设置应用程序图标 将一个ico图标放在资源文件夹下; 然后建立txt,输入 IDI_ICON1 DISCARABLE "myico.ico"; 保存文件,将其后缀改为.rc; ...
- java内功 ---- jvm虚拟机原理总结,侧重于虚拟机类加载执行系统
参考书籍:<深入理解java虚拟机>,三天时间用了八个小时看完,像读一本武侠小说,挺爽. 另外需声明:图片都是从我自己的csdn博客转载,所以虽然有csdn标识,但都是我自己画的图片. j ...
- 开源、免费功能全面的Chart图
简介: 每个前端都有一个Chart梦,至于真正去做的寥寥无几,无怪乎几个原因: 浏览器兼容问题 数据处理的一些算法,如自动计算坐标轴.自动排列文本等 流畅的动画 丰富的交互功能 去年一年的时间里,我一 ...
- leancloud 手机注册用户(调用API) 教程
// 从storybord 连线过来的button方法(注册按钮) - (IBAction)regist:(UIButton *)sender { AFHTTPSessionManager *mana ...
- Web Workers 的基本信息
问题:JavaScript 并行性 要将有趣的应用(例如从侧重服务器端的实施)移植到客户端 JavaScript,存在很多制约瓶颈.其中包括浏览器兼容性.静态类型.可访问性和性能.幸运的是,随着浏览器 ...
- 【原创】Newlife.XCode的常见功能使用(一)查询与数据初始化
本博客所有文章分类的总目录:http://www.cnblogs.com/asxinyu/p/4288836.html Newlife XCode组件相关文章目录:http://www ...
- 微信开发 -- 搭建基于ngrok的微信本地调试环境
第一步,安装ngrok客户端 (1)首先先到官网下载个客户端 http://natapp.cn/,选择适合的客户端类型,本人选择的是windows版 (2)下载后,解压,可以看到如下目录: 第二步,开 ...
- 基于Metronic的Bootstrap开发框架经验总结(1)-框架总览及菜单模块的处理
最近一直很多事情,博客停下来好久没写了,整理下思路,把最近研究的基于Metronic的Bootstrap开发框架进行经验的总结出来和大家分享下,同时也记录自己对Bootstrap开发的学习研究的点点滴 ...
- REST API出错响应的设计
REST API应用很多,一方面提供公共API的平台越来越多,比如微博.微信等:一方面移动应用盛行,为Web端.Android端.IOS端.PC端,搭建一个统一的后台,以REST API的形式提供服务 ...