ASP.NET 上的 Async/Await 简介

异步代码不是灵丹妙药

异步请求处理固然很有力,但它不会解决所有标题。关于 ASP.NET 上的 async 和
await 可以做什么样的难点,存在一些广泛的误会。

当有的开发人士明白 async 和 await
后,他们认为那是服务器代码“让步”于客户端(例如浏览器)的一种方法。不过,ASP.NET
上的 async 和 await 只“让步”于 ASP.NET 运行时;HTTP
协议保持不变,您对每个请求仍唯有一个响应。假使在 async/await 此前你要求SignalR 或 AJAX 或 UpdatePanel,那么在 async/await 之后照旧须求 SignalR
或 AJAX 或 UpdatePanel。

应用 async 和 await
的异步请求处理可以扶助扩张您的应用程序规模。然则,那是在一台服务器上的恢宏;您或许仍旧须要对扩张进行规划。若是您确实必要扩展连串布局,将照旧必要考虑无状态的幂等请求和有限辅助的行列。Async/await
多少抱有援助:它们使您可以足够利用服务器资源,所以您不必要平常举办增添。但是,如若您确实须求向外扩充,您将索要一个适龄的分布式体系布局。

ASP.NET 上的 async 和 await 都是有关 I/O
的。它们万分适合读取和写入文件、数据库记录和 REST
API。但是,它们不能很好地执行占用大量 CPU 的天职。您可以通过等待
Task.Run 开端有的背景工作,但如此做没有其余意义。事实上,通过启发式干扰ASP.NET 线程池会损害你的可扩充性。假如你要在 ASP.NET 上举行占用多量 CPU
的办事,最好的法子是直接在呼吁线程上执行该工作。经常,不要将工作排队送到
ASP.NET 上的线程池。

末段,在完全上考虑系统的可扩张性。十年前,常见的系统布局要有一个可与后端的
SQL Server 数据库举行通讯的 ASP.NET Web
服务器。在那种简易的系统布局中,经常数据库服务器是可扩大性的瓶颈,而不是
Web
服务器。让你的数据库调用异步可能起不到助手意义;当然你可以用它们来扩大Web 服务器,但数据库服务器将截留整个连串的增加。

Rick Anderson在他能够的博客文章中针对异步数据库调用给出案例,“我的数据库调用应该是异步的吧?”( bit.ly/1rw66UB)。以下是两点支撑论据:首先,异步代码有难度(由此开发人士的日子资产比只是购买较大的服务器要高);其次,如果数据库后端是瓶颈,那么扩张Web
服务器并未什么意思。在写那篇小说时,那两地方的实证丰裕有道理,但随着时间的推迟这三个论据的意思早已逐步减少。首先,使用
async 和 await
编写异步代码越发便于了。其次,随着满世界逐步选择云计算,网站的多少后端逐步得到扩大。诸如
Microsoft Azure SQL 数据库、NoSQL 以及别的 API 之类的现代后端与单个 SQL
Server 比较能够博得更进一步的扩张,从而将瓶颈又推回 Web
服务器。在那种场合下,async/await 可以经过伸张 ASP.NET 带来巨大的优势。

一道与异步请求处理

在长远商量异步请求处理程序此前,我想简要地回想同步请求处理程序在 ASP.NET
上的办事原理。在本例中,如若系统中的请求看重于有些外表资源,如数据库或
Web API。当接过请求时,ASP.NET
将内部的一个线程池线程分配给该请求。因为它是共同编写,所以恳请处理程序将一起调用该外部资源。那将堵住请求线程,直到回到对外表资源的调用。
1
 表达了富有三个线程的线程池,其中有一个线程被阻碍,正在等候外部资源。

NoSQL 1.png)
图 1 联合等待外部资源

最后,重临该外部资源的调用,并且呼吁线程恢复生机处理该请求。当成功该请求,且准备好发送响应时,请求线程将回到到线程池中。

那所有好倒是好,不过你的 ASP.NET
服务器得到的乞求总会超出它的线程可以处理的数额。那时候,额外的呼吁必须等到有线程可用时才可以运转。
2
 表明的仍是该双线程服务器,此时它接受多个请求。

NoSQL 2.png)
图 2 收到多个请求的双线程服务器

在那种场所下,前七个请求都被分配到线程池中的线程。每个请求都调用外部资源,于是阻止了它们的线程。第多个请求必须等待无线程可用时,才可以发轫进行拍卖,但该请求已经在系统中。它的计时器一向在工作,它正处在暴发HTTP 错误 503(服务不可用)的危急之中。

而是对此考虑一下:那第三个请求正在等候可用线程,与此同时系统中的其余三个线程实际上什么都没做。这几个线程受到掣肘,都在守候重回外部调用。它们确实并未做任何实际工作;它们不处在运行情形,也不占用其他CPU
时间。这几个线程被白白浪费掉,但还有请求处于须求中。以下是异步请求解决的情景。

异步请求处理程序的操作格局与此不一样。当接受请求时,ASP.NET
将其中的一个线程池线程分配给该请求。这几回,请求处理程序将异步调用该外部资源。当再次来到对表面资源的调用往日,已将此呼吁线程再次回到到线程池中。
3
 表明当请求在异步等待外部资源时具有四个线程的线程池。

NoSQL 3.png)
图 3 异步等待外部资源

主要的界别在于,在展开异步调用的历程中,已将请求线程重临到线程池中。当线程处于线程池中时,它不再与该请求相关联。此时,当重返外部资源调用时,ASP.NET
将其线程池中的一个线程重新分配给该请求。该线程将一连处理该请求。当呼吁达成时,该线程再次重回到线程池中。注意,对于联合处理程序,同一线程会用于该请求的满贯生命周期;相反,对于异步处理程序,可以将分裂的线程分配给同样请求(在差其余年华)。

现行,要是七个请求都进入,服务器就可以轻松处理了。因为每当请求在等候异步工作时,线程就会被放走到线程池中,它们可以肆意处理新的以及现有的伏乞。异步请求可以让多少较少的线程去处理数量较多的伸手。因而,ASP.NET
上的异步代码的第一优点是顶尖的可扩大性。

起来拔取

到底到终极了!准备好起来应用 async 和 await 了吗?我很欣赏你的耐性。

率先,查看本文的“异步代码不是灵丹妙药”一节以确保 async/await
对您的种类布局是造福的。接下来,将你的应用程序更新到 ASP.NET 4.5 并关闭
quirks
形式(此时若只为确保不暴发中断,运行它也得以)。那时,您便得以开首真的的协同/等待工作了。

从“叶”早先。想想你的伏乞什么开展处理并标识任何依据 I/O
的操作,更加是依照互联网的操作。常见的演示是数据库查询和指令,以及对其他Web 服务和 API 的调用。采取一个来伊始,并做一些考察来寻觅使用
async/await 执行该操作的最佳选拔。.NET Framework 4.5 中有无数平放 BCL
类型最近都已异步就绪;例如,SmtpClient 具有 SendMailAsync
方法。某些类型可以提供异步就绪更换;例如,HttpWebRequest 和 Web
客户端可以用 HttpClient 来更换。如须要,请升级您的库版本;例如,EF6
中的实体框架具有异步包容方法。

只是,要避免库中的“假异步”。假异步是这般一种现象:一个零件中享有一个异步就绪
API,而它只是通过将一块 API 封装在线程池线程内来兑现的。那对于实现ASP.NET 上的可增添性壮志未酬。假异步的一个超人示例就是 Newtonsoft
JSON.NET,一个其余方面很美妙的库。最好不调用(假)异步版本来举办 JSON
的系列化;只需换做调用同步版本即可。假异步的一个高难示例就是 BCL
文件流。当打开一个文件流时,必须以显式形式打开以便于异步访问;否则,它会采用假异步,同步阻止文件读取和写入操作中的线程池线程。

选拔一个“叶”之后,就足以开始选择代码中调用该 API
的格局,使之成为通过等待调用异步就绪 API 的异步方法。假如您调用的 API
协助 CancellationToken,您的法子应该使用 CancellationToken 并将其传递给
API 方法。

一旦将一个办法标记为异步,就相应变更其回到类型:void 变为“Task”,非 void
类型 T
变为“Task<T>”。您会发觉,所有该措施的调用者都急需变成异步,以使它们可以等待任务,等等。别的,将
Async 附加到你方法的名称中,以遵从基于任务的异步格局约定
bit.ly/1uBKGKR)。

允许 async/await 格局将你的调用堆栈向“Trunk”进行增添。在 Trunk
中,您的代码将与 ASP.NET 框架(MVC、Web 窗体,Web
API)相连接。阅读本文前面所述的“异步协助的现状”一节中的相关课程,将您的异步代码与框架举办合并。

附带找出任啥地点方线程的景象。由于异步请求可能会改变线程,所以地点线程状态(如
ThreadStaticAttribute、ThreadLocal<T>、线程数据插槽和
CallContext.GetData/SetData)将不可用。如若可能的话,请使用
HttpContext.Items 进行更换;或者你可以将不可变的数码存储在
CallContext.LogicalGetData/LogicalSetData 中。

以下是我意识的立见成效技巧:您可以(暂时)复制您的代码来创制一个垂直分区。利用那种技术,您不用将一块方法更改为异步;可以复制整个同步方法,然后将副本改为异步。然后,您可以让半数以上应用程序保持利用同步方法,只创造异步的一个小垂直片即可。假若你想把异步作为概念注明来进展探索或只针对应用程序的一有的举行负载测试来体会如何增加您的体系,那会是一个很棒的主意。您可以有所一个一心异步的请求(或页面),而应用程序的其他部分保持为共同。当然,您不期望对每一个主意都保存副本;最终,所有的
I/O 绑定代码都将是异步的,可以去除同步副本。

为什么没有了异步处理程序?

要是异步请求处理是那般完美,那它为何还不可用?事实上,异步代码卓殊适合扩充,由此从
Microsoft .NET Framework 形成之初到现行,ASP.NET
平台直接协助异步处理程序和模块。ASP.NET 2.0 引入了异步网页,ASP.NET MVC
2 中 MVC 得到了异步控制器。

不过,近期,异步代码在编排上接连有些标题,并且难于敬服。许多铺面便决定一起开发代码、支付更大的劳动器场或更高昂的托管,那样就会简单一些。现在,出现了逆袭:在
ASP.NET 4.5 中,使用 async 和 await
的异步代码大约与编制同步代码一样简单。由于大型系统迁移到云托管并须要尤其有规模,越来越多的集团开头器重ASP.NET 上的 async 和 await。

初稿链接

大部分关于 async/await
的在线资源假定你正在开发客户端应用程序,但在服务器上有 async
的职位吗?可以充裕肯定地回复“有”。本文是对 ASP.NET
上异步请求的概念性概述,并提供了对最佳在线资源的引用。我不打算介绍 async
或 await 的语法;因为自己已经在一篇介绍性的博客文章( bit.ly/19IkogW) 以及一篇有关 async
最佳做法的稿子
msdn.microsoft.com/magazine/jj991977)
中介绍过了。本文将专门紧要性介绍 async 在 ASP.NET 上的工作规律。

总结

自身梦想本文可以协助您精通 ASP.NET 上的异步请求的底子概念。使用 async 和
await,可以使编写能够最大限度地动用其服务器资源的 Web 应用程序、服务和
API 变得比往常任曾几何时候都更便于。Async 真是太棒了!

 

尊崇安全网

ASP.NET 4.5
中引入了多少个新的“安全网”,协助你捕捉应用程序中的异步难点。那一个是默认情状下存在的,应当保留。

当一头处理程序试图实施异步工作时,您的 InvalidOperationException
会收到这么的新闻,“此时不能开始异步操作”。有多少个基本点缘由促成出现此卓殊。第一,Web
窗体页有异步事件处理程序,但忽略了将 Page.Async 设置为
true。第二,同步代码调用 async void 方法。那是也是幸免 async void
的另一个缘由。

另一个安全网适用于异步处理程序:当异步处理程序达成请求,但 ASP.NET
检测到异步工作尚未到位时,您的 InvalidOperationException
会收到这么的音信,“异步模块或处理程序已成功,但异步操作依旧处在挂起状态”。那日常是出于异步代码调用
async void 方法而致使的,但也恐怕是因为不当使用基于事件的异步形式 (EAP)
组件 ( bit.ly/19VdUWu)。

您还足以利用一个采纳来关闭那八个安全网:HttpContext.AllowAsyncDuringSyncStages(也足以在
web.config 中对它举行设置)。Internet
上的片段页面提出你在收看这么些十分时开展如此的装置。我完全不相同意。说真的,我不知道这怎么可行。禁用安全网是一个吓人的想法。我可以想到的绝无仅有可能的来头是,您的代码可能早已拓展了一些充足进步的异步处理(远超我已经尝试过的限制),您是一个二十四线程处理的天分。所以,即使您曾经阅读了整篇文章,边打着呵欠边想,“拜托,我可不是菜鸟”,那么自己想你能够考虑禁用安全网。而对于大家中的其余人,那是一个至极危急的选项,除非您完全明白后果,否则不应举行此设置。

线程执行异步工作怎么?

自我直接被人问到这几个标题。那意味,必须有局部线程阻止对外表资源拓展 I/O
调用。由此,异步代码释放请求线程,但那不得不以献身系统中另一个线程为代价呢?没有,一点关联也没有。

要明白异步请求为何伸张,我将跟踪一个异步 I/O
调用的(简化)示例。如果有一个呼吁需求写入到文件中。请求线程调用异步写入措施。WriteAsync
由基类库 (BCL) 落成,并应用其异步 I/O 的完毕端口。由此,WriteAsync
调用会作为异步文件写入传递到 OS 中。然后,OS
与驱动程序堆栈进行通讯,同时传递数据以写入到 I/O 请求数据包 (IRP) 中。

现今,有趣的事务时有暴发了:如若设备驱动程序不能够立时处理
IRP,就不可能不异步举办拍卖。因此,驱动程序告诉磁盘先导写入,并将“挂起”响应再次回到到
OS 中。OS 将“挂起”响应传递到 BCL,然后 BCL
将一个不完整的义务再次来到到请求处理代码。请求处理代码等待将不完整的义务从该办法等处回来的职分。最后,请求处理代码最后向
ASP.NET 重回一个不完全的义务,并且呼吁线程被放飞回线程池中。

当今,考虑系统的近期处境。已经分配了各类 I/O 结构(例如,职责实例和
IRP),而且它们都处于挂起/不完整的情景。然则,没有其他线程因等待写入操作落成而遭受掣肘。ASP.NET、BCL、OS
以及配备驱动程序都并未专门用于异步工作的线程。

当磁盘完毕写入数据时,它通过暂停公告其驱动程序。该驱动程序会通告 OS 该
IRP 已经成功,并且 OS 会通过形成端口公告 BCL。线程池线程通过完毕从
WriteAsync
再次来到的职务来响应该文告;那反过来又復苏异步请求代码。在该到位布告阶段中,短时间“借用”了有的线程,但其实没有线程在写入进度中饱受阻碍。

本示例通过极大地简化,可是要点出色:真正的异步工作并不必要线程。实际推送字节也无需占用
CPU
时间。还有一个帮扶课程要打听。考虑一下在设施驱动程序的社会风气里,设备驱动程序怎么办才能即时或异步处理
IRP。同步处理是否一个选项。在配备驱动程序级别,所有重点的 I/O
都是异步的。许多开发人士的构思方式都是把用来 I/O 操作的“普通
API”认为是联合的,异步 API 作为一层建立在一般的同台 API
上。但是,那恰恰相反:实际上,普通 API是​​异步的;使用异步 I/O
已毕的是幸亏一起 API!

至于中止请求的升迁

当 ASP.NET
同步处理一个伸手时,它有一个万分简单的建制得以中止请求(例如,假若请求超出其超时值):它会搁浅该请求的干活线程。那是有道理的,因为在联名领域,每个请求从初叶到截至都利用同一个做事线程。中止线程对于
AppDomain 的悠久稳定而言尚不完美,由此默许意况下 ASP.NET
将为期回收您的应用程序,以维持到底。

对此异步请求,即使要暂停请求,ASP.NET
并不会半途而返工作线程。相反,它会收回使用 CancellationToken
的伸手。异步请求处理程序应该接受并坚守裁撤标记。一大半较新的框架(包涵Web API、MVC 和 SignalR)将创设并直接向你传递
CancellationToken;您要求做的就是把它讲明为一个参数。您也足以直接访问
ASP.NET 标记;例如,HttpRequest.提姆edOutToken 是当呼吁超时时打消的一个
CancellationToken。

乘机应用程序迁移到云,中止请求就浮现更为主要。基于云的应用程序也愈发看重于可能占用任意时间量的表面服务。例如,一种标准方式是使用指数回退来重试外部请求;倘诺你的应用程序器重于类似那样的各样劳动,对您的哀告处理在总体上采取一个逾期上限不失为一个好情势。

NoSQL,对此客户端应用程序,如 Windows 应用商店、Windows 桌面和 Windows Phone
应用程序,async 的严重性优点是完美的响应能力。这个项目标应用程序使用 async
首如若为着确保用户界面的响应能力。对于服务器应用程序,async
异步的主要优点是不易的可伸张性。Node.js
可增加性的要紧是其原本的异步本质;Open Web Interface for .NET (OWIN)
针对异步举办了崭新设计;ASP.NET 也得以是异步的。Async:不仅仅适用于 UI
应用程序!

怎么不增添线程池的尺寸?

那时候,总是会被问到:为啥不伸张线程池的高低?答案有三个:与阻碍线程池线程比较,异步代码扩张得更深层且更快。

异步代码的扩大性当先拦截线程,那是因为它接纳的内存更少;在现世操作系统上每个线程池线程具有
1MB
的仓库,外加一个不分页的水源堆栈。那听起来好像很多,但当你的服务器上有一大堆线程时,会意识其实不够用。与此相反,异步操作的内存开销要小得多。因此,使用异步操作的呼吁比选取阻止线程的伸手面临更少的内存压力。异步代码使你可以将越多的内存用于其它义务(例如缓存)。

异步代码在进程上比阻止线程更快,因为线程池的流入速度有限。停止发稿时,该速度为每两分钟一个线程。注入速度有限是件善事;它幸免了纷来沓至的线程打造和破坏。但是,考虑一下请求蜂拥而来时会暴发什么。同步代码很简单就会沦为瘫痪,因为请求将用光所有可用的线程,其他请求必须等待线程池有新的线程注入。而另一方面,异步代码不必要有如此的范围;它是“始终开放”的,可以这么说。异步代码能够更美丽地响应请求量突然波动。

请牢记,异步代码不会取代线程池。不应有唯有线程池或异步代码;而是要同时拥有线程池和异步代码。异步代码可以让你的应用程序充裕利用线程池。它选取现有的线程池,并把它升高到
11。

Async 支持的现状

本着 async 的包容性难题,已对许多库开展了更新。在本子 6 中已将 async
帮衬添加到实体框架(在 EntityFramework NuGet
程序包中)。然则,当以异步方式运行时,您必须求小心操作防止止延迟加载,因为延迟加载总是以协同方式履行。HttpClient(在
Microsoft.Net.Http NuGet 程序包中)是选取 async 理念设计而成的当代 HTTP
客户端,是调用外部 REST API 的出色拔取;是 HttpWebRequest 和 WebClient
的现代版替代品。在 2.1 版本中,Microsoft Azure 存储客户端库(在
WindowsAzure.Storage NuGet 程序包中)添加了异步匡助。

较新的框架(如 Web API 和 SignalR)对 async 和 await
提供周详的支撑。个别 Web API 已围绕 async
辅助建立起任何管道:不仅有异步控制器,还有异步筛选器和处理程序。Web API
和 SignalR 有一个很经常的异步故事:您可以“放手去做”然后“就会马到功成”。

那给大家带来了一个令人伤感的故事:近年来,ASP.NET MVC 只是有些扶助 async
和 await。有基本的支持——异步控制器的操作和收回工作健康。ASP.NET
网站上有关于怎样使用 ASP.NET MVC 中的异步控制器操作的优秀教程
bit.ly/1m1LXTx);那对于 MVC 上的 async
入门是绝佳的资源。不幸的是,ASP.NET MVC (近期)不协理异步筛选器
bit.ly/1oAyHLc) 和异步子操作
bit.ly/1px47RG)。

ASP.NET Web 窗体是一个较旧的框架,但它也固然帮助 async 和
await。并且,ASP.NET 网站上关于异步 Web 窗体的学科也是入门的绝佳资源
bit.ly/Ydho7W)。有了 Web
窗体,异步协理可以选用加入。您必须先将 Page.Async 设置为
true,然后您可以采用 PageAsyncTask
通过该页面注册异步工作(或者,您可以应用 async void
事件处理程序)。PageAsyncTask 也匡助撤除。

假设你有一个自定义 HTTP 处理程序或 HTTP 模块,那么 ASP.NET
现在也可以支撑它们的异步版本。HTTP 处理程序是通过 HttpTaskAsyncHandler
bit.ly/1nWpWFj) 举办扶助的,HTTP 模块是由此伊夫ntHandlerTaskAsyncHelper ( bit.ly/1m1Sn4O)
进行支撑的。

以至发稿时,ASP.NET 团队正在开发名为 ASP.NET vNext 的一个新品类。在
vNext 中,默许情形下全方位管道是异步的。近年来,该安插将 MVC 和 Web API
合并到能够周到扶助async/await(包罗异步筛选器和异步视图组件)的单一框架中。其余异步就绪框架(如
SignalR)会在 vNext 中找到一个自然的家。当然,将来是 async 的众人。

在先导以前

先是你须求掌握惟有 ASP.NET 4.5 匡助 async 和 await。有一个誉为
Microsoft.Bcl.Async 的 NuGet 程序包可为 .NET Framework 4 启用 async 和
await,但并不行使它;那将无法正常干活!其原因是,为了能与 async 和 await
更好地联手坐班,ASP.NET 本身必须更改其管理异步请求处理的法子;NuGet
程序包中蕴藏编译器需求的享有品类,但不会修补 ASP.NET
运行时。没有缓解办法;您需求 ASP.NET 4.5 或更高版本。

接下去,要了解,ASP.NET 4.5 在服务器上引入了“quirks
形式”。借使您创造一个新的 ASP.NET 4.5
项目,则不用担心。可是,若是要将现有的系列升级到 ASP.NET 4.5,所有 quirk
都将被打开。我提议您​​通过编制 web.config 并将
httpRuntime.targetFramework 设置为 4.5
把它们整个关门。若是运用此设置的应用程序败北(并且您不想花时间去修补它),至少你可以经过为
aspnet:UseTaskFriendlySynchronizationContext 的 appSetting
键添加值“true”来赢得 async/await 工作。若是您将
httpRuntime.targetFramework 设置为 4.5,则 appSetting 键不需要。Web
开发团队已在bit.ly/1pbmnzK 公布一篇有关这一新的“quirks
方式”的详细音讯的博客。提示:
借使你收看出现意外的行为或例外景况,并且您的调用堆栈包含LegacyAspNetSynchronizationContext,那么你的应用程序正在这一个“quirks
情势”下运作。LegacyAspNetSynchronizationContext 与异步不包容;您在
ASP.NET 4.5 上需求正常的 AspNetSynchronizationContext。

在 ASP.NET 4.5 中,所有的 ASP.NET
设置都对准异步请求设置了很好的默认值,但也有几个其他装置您可能要改变。首先是
IIS 设置:考虑将 IIS/HTTP.sys
的队列限制(应用程序池|高级设置|队列长度)从默许的 1,000 升高到
5,000。另一个是 .NET
运行时设置:ServicePointManager.DefaultConnectionLimit,它的默许值是水源数量的
12 倍。DefaultConnectionLimit 限制到同一主机名的传遍连接数。

网站地图xml地图