集成OWIN与Autofac的ASP.NET Web API 2请求取消后代码执行异常
OWIN+Autofac集成的ASP.NET Web API 2中,客户端取消请求后await后续代码无法执行的问题
问题重现
我正在开发一个基于OWIN和Autofac的ASP.NET Web API 2应用,遇到以下异常行为:
预期流程
- 浏览器发起请求
- 服务器启动长时间异步操作(
await Task.Delay(10000)) - 浏览器在5秒后中止请求
- 服务器完成
Task.Delay执行 ContinueWith回调及await之后的代码正常执行
实际流程
- 浏览器发起请求
- 服务器启动长时间异步操作
- 浏览器在5秒后中止请求
- 服务器立即返回500响应,未等待
Task.Delay完成 - 10秒后仅
ContinueWith方法执行,await之后的代码完全不运行
该问题仅在同时集成OWIN与Autofac的Web API 2项目中出现,单独使用Web API 2、Web API 2+Autofac、Web API 2+OWIN的配置下均能得到预期结果。
相关代码
API控制器代码
[HttpGet] [Route("test")] public async Task<string> GetAll() { await DoLongRunningOperationAsync(CancellationToken.None); return "Success"; } private async Task DoLongRunningOperationAsync(CancellationToken cancellationToken) { try { Debug.WriteLine("Starting delay"); await Task.Delay(10000, cancellationToken) .ContinueWith(task => { Debug.WriteLine("Continue With"); }); Debug.WriteLine("Delay completed successfully"); } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { Debug.WriteLine("Delay was canceled"); throw; } }
OWIN启动配置
public void Configuration(IAppBuilder app) { var config = new HttpConfiguration(); config.MapHttpAttributeRoutes(); var builder = new ContainerBuilder(); builder.RegisterApiControllers(Assembly.GetExecutingAssembly()); builder.RegisterType<TestService>().As<ITestService>().InstancePerRequest(); var container = builder.Build(); config.DependencyResolver = new AutofacWebApiDependencyResolver(container); app.UseAutofacMiddleware(container); app.UseAutofacWebApi(config); app.UseWebApi(config); }
客户端测试代码
var xhttp = new XMLHttpRequest(); xhttp.open("GET", "http://localhost:59475/test/test"); xhttp.send(); setTimeout(() => xhttp.abort(), 5000)
原因分析
- 请求上下文与Autofac生命周期绑定:Autofac的
InstancePerRequest会将服务生命周期绑定到OWIN请求上下文,当客户端取消请求时,OWIN会立即终止请求上下文,Autofac随之释放请求范围内的所有服务实例。此时await完成后尝试回到原请求上下文执行后续代码时,上下文已被销毁,导致代码无法继续执行。 - 上下文同步问题:未使用
ConfigureAwait(false)时,await默认会尝试回到原请求上下文(包含Autofac请求容器),而该上下文在请求取消后已被清理,直接导致后续代码执行失败。 - ContinueWith的特殊性:
ContinueWith默认在后台线程池执行,不依赖原请求上下文,因此即使请求上下文被销毁,仍能正常运行。
解决方案
方案1:解除异步代码与请求上下文的绑定
在await调用时添加ConfigureAwait(false),避免回到已被销毁的请求上下文:
await Task.Delay(10000, cancellationToken) .ContinueWith(task => { Debug.WriteLine("Continue With"); }).ConfigureAwait(false);
方案2:将长时间操作移至后台任务(不依赖请求上下文)
如果后续代码需要访问服务实例,不要使用请求范围内的服务,而是将逻辑移到独立于请求的后台任务中,例如使用Task.Run并确保不捕获请求上下文:
private async Task DoLongRunningOperationAsync(CancellationToken cancellationToken) { try { Debug.WriteLine("Starting delay"); // 用Task.Run将操作移至后台线程,脱离请求上下文 await Task.Run(async () => { await Task.Delay(10000, cancellationToken); Debug.WriteLine("Continue With"); }, cancellationToken).ConfigureAwait(false); Debug.WriteLine("Delay completed successfully"); } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { Debug.WriteLine("Delay was canceled"); throw; } }
方案3:调整Autofac中间件行为(不推荐)
不建议修改Autofac默认的请求生命周期管理,但如果必须在请求取消后保留容器,可以自定义Autofac的OWIN中间件,延迟请求容器的释放。不过这种做法可能导致资源泄漏,需谨慎使用。
验证步骤
- 应用上述方案之一修改代码
- 启动Web API服务,用客户端代码发起请求并在5秒后取消
- 查看Debug输出,确认
Delay completed successfully日志正常打印 - 检查服务器响应,确认即使客户端取消,异步逻辑仍能完整执行
内容的提问来源于stack exchange,提问作者user25652029
相关产品推荐
相关产品推荐

