我们正在开发一个整体式 Web 应用程序 - 非常有状态。它处理 HTTP 请求和长期存在的 SignalR 连接。 (在 ASP.NET Core 3.1 中 - 我们稍后将升级到 .NET 5。)
我们从登录页面重定向到我们的“主页”。主页需要一段时间来加载和初始化,之后它会与 SignalR 连接。我们在服务器端还有很多工作要做。在登录请求中执行服务器工作(在重定向到主页之前)会减慢登录速度。
“哦,那我们就用任务吧!”,我想。也就是说,将服务器工作放在一个任务中,将其保存在用户状态中,并让它与主页面的加载并行执行。像这样(简化):
public static async Task ServerSideInit()
{
// do a lot of init work
}
// at the end of the controller handling the login page POST:
UserState.BackgroundTask = ServerSideInit();
Redirect(UrlToTheMainPage);
// when the main page connects via SignalR:
try {
await UserState.BackgroundTask;
}
catch {
// handle errors in the init work
}
这真的会加快速度。页面加载还是初始化工作先完成并不重要——我们等待任务。 ServerSideInit() 中的工作并不重要。如果发生某些事情并且主页永远不会连接,则 UserState(和 Task)将在超时后被销毁——这完全没问题。 (有一些注意事项。例如,我们必须使用 IServiceProvider 在 ServerSideInit() 中创建/处置作用域,因此我们在控制器之外获得了作用域 DbContext。但这没关系。)
但是我读到 ASP.NET Core 框架在结束 POST 请求时存在关闭任务的风险! (Do you have to await async methods?) 简单的 HostingEnvironment.QueueBackgroundWorkItem 不再可用。不过,有一个新的 BackgroundService 类。 (https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio) 但是注册服务和排队作业似乎是一个非常麻烦的解决方案......我们只想触发一个需要几秒钟才能完成的任务,并在 ASP.NET Core 完成后让它继续运行处理完 POST 请求。
我对 ASP.NET Core 的经验不是很丰富……所以我非常感谢您提供一些意见!我的简单解决方案不起作用吗?任务会被框架终止吗?有没有更简单的方法来告诉框架“请不要碰这个任务”?或者 BackgroundService 是要走的路吗?
答案 0 :(得分:3)
在登录请求中执行服务器工作(在重定向到主页之前)会减慢登录速度。 “哦,那我们就用任务吧!”,我想。也就是把服务端的工作放在一个Task中,保存在用户态,让它和主页面的加载并行执行。
因此,您需要进行请求外在工作。即,您的服务器所做的工作是在请求范围之外。
您需要问自己的第一个问题是“这项工作需要完成吗?”换句话说,“我可以偶尔失去工作吗?”。如果出于正确性原因必须完成这项工作,那么只有一个真正的解决方案:asynchronous messaging。如果您对偶尔丢失工作感到满意(例如,如果主页会检测到 ServerSideInit
未完成并会在那时完成),那么您真正谈论的是缓存,并且有一个内存解决方案很好。
但后来我读到 ASP.NET Core 框架在结束 POST 请求时存在关闭任务的风险!
首先要认识到的是 shutdowns are normal。在常规部署、操作系统补丁等期间滚动更新......您的网络服务器迟早会自愿关闭,任何假设它会永远运行的代码本质上都是有缺陷的。
默认情况下,当所有请求都得到响应时,ASP.NET Core 会认为自己“可以安全关闭”。这是任何 HTTP 服务的合理行为,并且此逻辑扩展到每个 HTTP 框架,无论语言或运行时如何。但是,这显然是请求外部代码的问题。
因此,如果您的代码只是通过直接调用方法(或通过 Task.Run
,另一个可悲的流行选项)来启动任务,那么它就很危险:ASP.NET 甚至不知道请求外部代码存在,并且会在请求时愉快地退出,突然终止该代码。
有一些权宜之计,例如 HostingEnvironment.QueueBackgroundWorkItem
(pre-Core) 和 IHostedService
/ IHostApplicationLifetime
(Core)。它们注册请求外部代码,以便 ASP.NET 知道它,并且在该代码完成之前不会关闭。然而,这些解决方案只能做到一半。因为它们在内存中,所以它们也很危险:ASP.NET 现在知道请求外部代码,但 HTTP 代理、负载平衡器和部署脚本不知道。
有没有更简单的方法告诉框架“请不要碰这个任务”?
回到这个回答开头的问题:“这个工作需要完成吗?”
如果这只是一个优化而不需要,那么只需使用 Task.Run
(或 IHostedService
)启动工作就足够了。不过,我不会将它保留在 UserState
中,因为 Task
不可序列化。
如果工作需要完成,那么构建一个asynchronous messaging解决方案。