背景
已经有很多文章记录了 web程序中采用异步编程的优势和.Net异步编程的用法, 异步编程虽然不能解决查询数据库的瓶颈, 但是利用线程切换,能最大限度的弹性利用工作线程, 提高了web服务的响应能力。
【 9012年了,再不会异步编程你是真老了】
本文要说的是利用异步编程中的取消机制缓解数据库的查询瓶颈,开发者只需在 MVC/WebAPI查询方法体内关注CancllationToken并适时取消异步任务, 这将大大提高应用的响应能力。
头脑风暴
想象你请求某网站页面,该页面正闪着菊花试图努力绽放(正在加载),最终你忍不了:
① F5刷新
② 转向其他页面
③ 点击浏览器“停止”按钮
对于可怜的服务器,用户快速刷新5次,服务器将被迫接受 5倍的工作量,这是因为即使用户刷新了浏览器(或点击停止按钮), 虽然取消了原始浏览器请求,但是Web服务器并不Care,仍然按部就班处理进入HTTP pipeline的请求(MVC/WebAPI 中默认行为)。其他②③场景类似。
在异步编程中能向任务发出Cancllation信号,停止web服务器一切后端查询行为。在.NET中,这是使用CancellationToken完成的:
取消令牌的实例传递到异步任务
异步任务监视令牌,以查看请求是否已经被取消。
如果请求取消,则应停止执行正在执行的操作。.NET中的大多数异步方法将具有接受取消令牌的重载。
AspNetCore实践
P1 监测CancellationToken令牌
访问 MyReallySlowReport页面,等待5s,最终他们放弃了,去了其他页面:
所有正在进行的请求都将被取消。
MVC/WebAPI能接受到取消请求的信号。开发者只需要在Controller Action中添加CancellationToken参数,并在后续行为中监测该取消信号。
public async Task<ActionResult> MyReallySlowReport(CancellationToken cancellationToken) { List<ReportItem> items; using (ApplicationDbContext context = new ApplicationDbContext()) { items = await context.ReportItems.ToListAsync(cancellationToken); } return View(items); }
很容易取消SQL的查询行为,因为上述EF的调用api支持取消异步操作; 对于自定义的长耗时查询行为,可以使用CancllationToken的原生触发用法:
public async Task<ActionResult> MyReallySlowReport(CancellationToken cancellationToken) { List<ReportItem> items; using (ApplicationDbContext context = new ApplicationDbContext()) { items = await context.ReportItems.ToListAsync(cancellationToken); } foreach (var item in items) { cancellationToken.ThrowIfCancellationRequested(); // slow non-cancellable work Thread.Sleep(1000); } return View(items); }
P2 处理取消异步操作向上抛出的异常
Web服务器触发取消信号,一般会向上会抛出 OperationCanceledException 或者 TaskCancellationException,所以为了记录这种非常规异常,建议采用独立的ExceptionFilter记录。
public class OperationCancelledExceptionFilter : ExceptionFilterAttribute { private readonly ILogger _logger; public OperationCancelledExceptionFilter(ILoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger<OperationCancelledExceptionFilter>(); } public override void OnException(ExceptionContext context) { if(context.Exception is OperationCanceledException) { _logger.LogInformation("Request was cancelled"); context.ExceptionHandled = true; context.Result = new StatusCodeResult(400); } } }
P3 想要得到CTO的称赞,可不是那么简单。
以上只是后端程序员利用取消机制缓解异步查询瓶颈的后端操作,从web应用全流程角度思考,这个优化还能提升吗?
> 以上是传统的网页请求场景,在取消请求时,浏览器帮助我们发起了Cancellation信号。
> 想想日益常见的SPA程序(单页面程序),绝大部分页面请求都是Ajax请求,你点击应用的另外一个“页面(JS代码维护页面导航),浏览器不会自动取消请求。
所以在SPA应用中,要前端自行发出取消请求的信号:
var xhr = $.get("/api/myslowreport", function(data){ //show the data }); //If the user navigates away from this page xhr.abort()
That‘s all ,前后端程序猿通力配合, 应用的吞吐量和响应能力极大提升, CTO要给各位加薪了。