MVC后台进程运行时阻塞页面跳转,使用Task.Run等方法无效应如何解决
后台任务阻塞请求问题解决方案
核心原因
你用Task.Run和HostingEnvironment.QueueBackgroundWorkItem没有生效,主要是以下几个常见问题导致:
- 代码中直接把当前请求作用域的Dynamics 365服务实例
svc传到了后台任务,请求返回后这个实例会被DI容器销毁,后台线程调用已销毁的服务会触发未处理异常,导致线程阻塞或进程卡住。 - ASP.NET默认对同一会话的所有请求加排他锁,同用户的请求会串行执行,如果控制器开启了可写会话,或者后台任务持有会话引用,就会导致其他请求排队无法加载。
- 后台任务中的异步调用没有禁用同步上下文捕获,占用ASP.NET请求同步上下文导致阻塞。
- 后台任务直接访问已结束请求的
HttpContext、Session等对象,触发未处理异常。
修复步骤
- 所有需要传到后台任务的参数(比如示例中的
guide.Id)提前提取为值类型,不要直接引用控制器作用域的引用类型对象,禁止后台任务访问当前请求的HttpContext、Session等资源。 - 给控制器添加
[SessionState(SessionStateBehavior.ReadOnly)]特性,如不需要使用会话可设置为Disabled,关闭同会话请求的排他锁。 - 后台任务使用独立的服务作用域创建Dynamics 365服务实例,不要复用当前请求的
svc实例。 - 后台任务内所有异步调用添加
ConfigureAwait(false),禁用同步上下文捕获。
修正后代码示例
// 关闭会话排他锁 [SessionState(SessionStateBehavior.ReadOnly)] public class MeetingController : Controller { // 注入服务作用域工厂,用于创建独立于请求的服务作用域 private readonly IServiceScopeFactory _serviceScopeFactory; public MeetingController(IServiceScopeFactory serviceScopeFactory) { _serviceScopeFactory = serviceScopeFactory; } public ActionResult ConfirmCurrentMonthMeetings() { // 提前提取所有需要的参数,不要在后台任务中直接引用guide对象 var currentGuideId = guide.Id; HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken => { // 创建独立服务作用域,生命周期和当前请求完全隔离 using var independentScope = _serviceScopeFactory.CreateScope(); // 在独立作用域内获取Dynamics 365服务实例 var dynamicsSvc = independentScope.ServiceProvider.GetRequiredService<IDynamics365Service>(); // 执行长任务,异步调用加ConfigureAwait(false) await QueryHelper.MarkReadOnlyCurrentMonthMeetings(currentGuideId, dynamicsSvc, cancellationToken).ConfigureAwait(false); }); ViewBag.ConfirmingMeetingsMessage = "会议正在后台确认中,该操作可能需要几分钟时间"; PrepareMainScreen(); return View("MainScreen"); } }
额外注意事项
如果
MarkReadOnlyCurrentMonthMeetings目前是同步实现,建议改造成异步方法,避免长时间占用工作线程导致线程池饥饿,进一步影响其他请求的响应速度。
内容的提问来源于stack exchange,提问作者Luis Gómez Ruiz
相关产品推荐
相关产品推荐

