博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
走进异步世界-犯傻也值得分享:ConfigureAwait(false)使用经验分享
阅读量:5071 次
发布时间:2019-06-12

本文共 3747 字,大约阅读时间需要 12 分钟。

在上周解决“”的过程中,我们干了一件自以为很有成就感的事——在表现层(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)是干傻事,整个异步化改造就是一件大傻事。改造过程很艰辛,多次犹豫是不是要这么彻底地异步化,最终还是坚持了下来。当解决了之后,如释重负!异步化改造效果明显,超出了预期——响应性能与吞吐能力都得到了提升。过程中所有的煎熬与痛苦都被成功后涌上心头的那种兴奋所秒杀。

【参考资料】

转载于:https://www.cnblogs.com/65001134/p/3838236.html

你可能感兴趣的文章
jquery实现限制textarea输入字数
查看>>
thinkphp5 csv格式导入导出(多数据处理)
查看>>
PHP上传RAR压缩包并解压目录
查看>>
Codeforces 719B Anatoly and Cockroaches
查看>>
jenkins常用插件汇总
查看>>
c# 泛型+反射
查看>>
第九章 前后查找
查看>>
Python学习资料
查看>>
多服务器操作利器 - Polysh
查看>>
[LeetCode] Candy
查看>>
Jmeter学习系列----3 配置元件之计数器
查看>>
jQuery 自定义函数
查看>>
jq 杂
查看>>
jquery datagrid 后台获取datatable处理成正确的json字符串
查看>>
作业一
查看>>
AJAX
查看>>
ActiveMQ与spring整合
查看>>
web服务器
查看>>
Git的使用--打tag
查看>>
F# 编程 借助 F# 构建 MVVM 应用程序
查看>>