ASP.NET Core 中断请求了解一下(翻译)
本文所讲方式仅适用于托管在Kestrel Server中的应用。如果托管在IIS和IIS Express上时,ASP.NET Core Module(ANCM)并不会告诉ASP.NET Core在客户端断开连接时中止请求。但可喜的是,ANCM预计在.NET Core 2.2中会完善这一机制。
1. 引言
假设有一个耗时的Action,在浏览器发出请求返回响应之前,如果刷新了页面,对于浏览器(客户端)来说前一个请求就会被终止。而对于服务端来说,又是怎样呢?前一个请求也会自动终止,还是会继续运行呢?
下面我们通过实例寻求答案。
2. 实例演示
创建一个SlowRequestController,再定义一个Get请求,并通过Task.Delay(10_000)模拟耗时行为。代码如下:
public class SlowRequestController : Controller
{
private readonly ILogger _logger;
public SlowRequestController(ILogger<SlowRequestController> logger)
{
_logger = logger;
}
[HttpGet("/slowtest")]
public async Task<string> Get()
{
_logger.LogInformation("Starting to do slow work");
// slow async action, e.g. call external api
await Task.Delay(10_000);
var message = "Finished slow delay of 10 seconds.";
_logger.LogInformation(message);
return message;
}
}
如果我们发起请求,那么该页面将耗时10s才能完成显示。

如果我们检查运行日志,我们发现其输出符合预期:

如果在第一次请求返回之前,刷新页面,结果将是怎样呢??

从日志中我们可以看出:刷新后,第一个请求虽然在客户端被取消了,但是服务端仍旧会持续运行。
从而可以说明MVC的默认行为: 即使用户刷新了浏览器会取消原始请求,但MVC对其一无所知,已经被取消的请求还是会在服务端继续运行,而最终的运行结果将会被丢弃。
这样就会造成严重的性能浪费。如果服务端能感知用户中断了请求,并终止运行耗时的任务就好了。
幸好,ASP.NET Core开发团队体贴的考虑了这一点,允许我们通过以下两种方式来获取客户端的请求是否被终止。
- 通过
HttpContex的RequestAborted属性: - 通过方法注入
CancellationToken参数:
if (HttpContext.RequestAborted.IsCancellationRequested)
{
// can stop working now
}
[HttpGet]
public async Task<ActionResult> GetHardWork(CancellationToken cancellationToken)
{
// ...
if (cancellationToken.IsCancellationRequested)
{
// stop!
}
// ...
}
而这两种方式其实是一样的,因为HttpContext.RequestAborted和cancellationToken对应的是同一个对象:
if(cancellationToken == HttpContext.RequestAborted)
{
// this is true!
}
下面我们就来以cancellationToken为例,看看如何感知客户端请求终止并终止服务端服务。
3. 在Action中使用CancellationToken
CancellationToken是由CancellationTokenSource创建的轻量级对象。当某个CancellationTokenSource被取消时,它会通知所有的消费者CancellationToken。
取消时,CancellationToken的IsCancellationRequested属性将设置为True,表示CancellationTokenSource已取消。
再回到前面的实例,我们有一个长期运行的操作方法(例如,通过调用许多其他API生成只读报告)。由于它是一种昂贵的方法,我们希望在用户取消请求时尽快停止执行操作。
下面的代码显示了通过在action方法中注入一个CancellationToken,并将其传递给Task.Delay,来达到同步终止服务端请求的目的:
public class SlowRequestController : Controller
{
private readonly ILogger _logger;
public SlowRequestController(ILogger<SlowRequestController> logger)
{
_logger = logger;
}
[HttpGet("/slowtest")]
public async Task<string> Get(CancellationToken cancellationToken)
{
_logger.LogInformation("Starting to do slow work");
// slow async action, e.g. call external api
await Task.Delay(10_000, cancellationToken);
var message = "Finished slow delay of 10 seconds.";
_logger.LogInformation(message);
return message;
}
}
MVC将使用CancellationTokenModelBinder自动将Action中的任何CancellationToken参数绑定到HttpContext.RequestAborted。当我们在Startup.ConfigureServices()中调用services.AddMvc() 或 services.AddMvcCore()时,CancellationTokenModelBinder模型绑定器就会被自动注册。
通过这个小改动,我们再尝试在第一个请求返回之前刷新页面,从日志中我们发现,第一个请求将不会继续完成。而是当Task.Delay检测到CancellationToken.IsCancellationRequested属性为true时立即停止执行时并抛出TaskCancelledException。

简而言之,用户刷新浏览器,在服务端通过抛出TaskCancelledException异常终止了第一个请求,而该异常通过请求管道再传播回来。
在这个场景中,Task.Delay()会监视CancellationToken,因此无需我们手动检查CancellationToken是否被取消。
4. 手动检查CancellationToken状态
如果你正在调用支持CancellationToken的内置方法,比如Task.Delay()或HttpClient.SendAsync(),那么你可以直接传入CancellationToken,并让内部方法负责实际取消。
在其他情况下,您可能正在进行一些同步工作,您希望能够取消这些工作。例如,假设正在构建一份报告来计算公司员工的所有佣金。你循环每个员工,然后遍历他们的每一笔销售。
能够在中途取消此报告生成的简单解决方案是检查for循环内的CancellationToken,如果用户取消请求则跳出循环。
以下示例通过循环10次并执行某些同步(不可取消)工作来表示此类情况,该工作由对Thread.Sleep()来模拟。在每个循环开始时,我们检查CancellationToken,如果取消则抛出异常。这使得我们可以终止一个长时间运行的同步任务。
public class SlowRequestController : Controller
{
private readonly ILogger _logger;
public SlowRequestController(ILogger<SlowRequestController> logger)
{
_logger = logger;
}
[HttpGet("/slowtest")]
public async Task<string> Get(CancellationToken cancellationToken)
{
_logger.LogInformation("Starting to do slow work");
for(var i=0; i<10; i++)
{
cancellationToken.ThrowIfCancellationRequested();
// slow non-cancellable work
Thread.Sleep(1000);
}
var message = "Finished slow delay of 10 seconds.";
_logger.LogInformation(message);
return message;
}
}
现在,如果你取消请求,则对ThrowIfCancelletionRequested()的调用将抛出一个OperationCanceledException,它将再次传播回过滤器管道和中间件管道。
5. 使用ExceptionFilter捕捉取消异常
ExceptionFilters是一个MVC概念,可用于处理在您的操作方法或操作过滤器中发生的异常。可以参考官方文档。
可以将过滤器应用到控制器级别和操作级别,也可以应用于全局级别。为了简单起见,我们创建一个过滤器并添加到全局过滤器。
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(499);
}
}
}
我们通过重载OnException方法并特殊处理OperationCanceledException异常即可成功捕获取消异常。
Task.Delay()抛出的异常是TaskCancelledException类型,其为OperationCanceledException的基类,所以,以上过滤器也可正确捕捉。
然后注册过滤器:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Filters.Add<OperationCancelledExceptionFilter>();
});
}
}
现在再测试,我们发现运行日志将不会包含异常信息,取而代之的是我们自定义的信息。

6. 最后
通过本文,我们知道用户可以通过点击浏览器上的停止或重新加载按钮随时取消Web应用的请求。而实际上仅仅是终止了客户端的请求,服务端的请求还在继续运行。对于简单耗时短的请求来说,我们可以不予理睬。但是,对于耗时任务来说,我们却不可以置若罔闻,因为其有很高的性能损耗。
而如何解决呢?其关键是通过CancellationToken来捕捉用户请求的状态,从而根据需要进行相应的处理。
参考资料:
CancellationTokens and Aborted ASP.NET Core Requests
Using CancellationTokens in ASP.NET Core MVC controllers
ASP.NET Core 中断请求了解一下(翻译)的更多相关文章
- ASP.NET Core 中文文档
ASP.NET Core 中文文档 翻译计划 五月中旬 .NET Core RC2 如期发布,我们遂决定翻译 ASP.NET Core 文档.我们在 何镇汐先生. 悲梦先生. 张仁建先生和 雷欧纳德先 ...
- 新的ASP.NET Core 迁移指南
最近在微信里做了一个调查: Web Forms应用程序升级到.NET 6, 收到550份调查,调查还在继续,欢迎参与调查.可以访问链接:https://wj.qq.com/s2/9822949/ac3 ...
- ASP.NET Core文档中Work with Data章节的翻译目录
作为初学者看了相关的教程,遇到的问题有: 1. 教程不是针对初学者,往往在某一方面教的较深,但并不系统,不适合初学者: 2. 虽然翻译的很顺畅,但是谈了自己较多的开发体会,初学者看着困难,尤其是TOM ...
- 【翻译】Asp.net Core介绍
ASP.NET Core is a significant redesign of ASP.NET. This topic introduces the new concepts in ASP.NET ...
- [翻译] ASP.NET Core 2.2 正式版发布
本文为翻译,原文地址:https://blogs.msdn.microsoft.com/webdev/2018/12/04/asp-net-core-2-2-available-today/ 我(文章 ...
- 翻译 Asp.Net Core 2.2.0-preview1已经发布
Asp.Net Core 2.2.0-preview1已经发布 原文地址 ASP.NET Core 2.2.0-preview1 now available 今天我们很高兴地宣布,现在可以试用ASP. ...
- 【翻译】asp.net core中使用MediatR
这篇文章来自:https://ardalis.com/using-mediatr-in-aspnet-core-apps 本文作为翻译,有一些单词翻译成中文可能会有一些误解(对于读者)或者错误(对于作 ...
- [翻译] 初看 ASP.NET Core 3.0 即将到来的变化
[翻译] 初看 ASP.NET Core 3.0 即将到来的变化 原文: A first look at changes coming in ASP.NET Core 3.0 在我们努力完成下一个 m ...
- [翻译] 如何在 ASP.Net Core 中使用 Consul 来存储配置
[翻译] 如何在 ASP.Net Core 中使用 Consul 来存储配置 原文: USING CONSUL FOR STORING THE CONFIGURATION IN ASP.NET COR ...
随机推荐
- Java如何获取系统信息(包括操作系统、jvm、cpu、内存、硬盘、网络、io等)
1 下载安装sigar-1.6.4.zip 使用java自带的包获取系统数据,容易找不到包,尤其是内存信息不够准确,所以选择使用sigar获取系统信息. 下载地址:http://sourceforge ...
- Java 学习路线之四个阶段
写这篇总结,主要是记录下自己的学习经历,算是自己对知识的一个回顾.也给想要学习 Java 的提供一些参考,对于一些想要学习Java,又不知道从哪里下手,以及现在有哪些主流的 Java 技术.想必大家学 ...
- Debian9桌面设置
本文由荒原之梦原创,原文链接:http://zhaokaifeng.com/?p=665 新安装的Debian9桌面上啥都没有,就像这样: 图 1 虽然很简洁,但是用着不是很方便,下面我们就通过一些设 ...
- CAPTCHA---验证码 ---Security code
BotDetect Java CAPTCHA Generator 3. Add BotDetect Java CAPTCHA Library Dependency Here is how to add ...
- jmeter使用csv进行参数化(二)
上篇说的是csv的第一种方法进行参数化,这篇说第二种方法. 重新打开录制好的脚本. 1.提取函数变量 打开选项--函数助手对话框 设置对话框参数: 选择csvread,然后将变量文件的路径填写进来.添 ...
- 玩转spring MVC(九)---Spring Data JPA
偷个懒 在网上看有写的比较好的,直接贴个链接吧:http://***/forum/blogPost/list/7000.html 版权声明:本文为博主原创文章,未经博主允许不得转载.
- 那些年我们一起清除过的浮动float与clearfix
浮动(float),一个我们即爱又恨的属性.爱,因为通过浮动,我们能很方便地布局: 恨,浮动之后遗留下来太多的问题需要解决,特别是IE6-7(以下无特殊说明均指 windows 平台的 IE浏览器). ...
- java基于BasicPlayer调用 播放音乐
无聊中想想用java调用下听音乐的api.晚上很多文章用的比较老大方法了,都是用原生的代码写,而且不支持mp3格式,BasicPlayer第三方包提供了很好的api调用,简单的3行代码就可以调用mp3 ...
- Java Servlet 2.5 设置 cookie httponly
Servlet 3.0 有 cookie.setHttpOnly(true); 多么人性化, Servlet 2.5 是没有这个方法的要这个曲线救国:cookie.setPath("; Ht ...
- asp.net core系列 49 Identity 授权(上)
一.概述 授权是指用户能够访问资源的权限,如页面数据的查看.编辑.新增.删除.导出.下载等权限.ASP.NET Core 授权提供了多种且灵活的方式,包括:Razor pages授权约定.简单授权.R ...