重定向时让任务完成?

Letting a task complete while redirecting?

我们正在开发一个单体网络应用程序——非常有状态。它处理 HTTP 请求和长期存在的 SignalR 连接。 (在 ASP.NET Core 3.1 中——我们稍后将升级到 .NET 5。)

我们从登录页面重定向到我们的“主页”。主页需要一段时间来加载和初始化,然后它会与 SignalR 连接。我们在服务器端还有很多工作要做。在登录请求中执行服务器工作(在重定向到主页之前)会减慢登录速度。

“哦,那么让我们使用任务吧!”,我想。即把服务端的工作放在一个Task中,保存在用户态,让它和主页面的加载并行执行。像这样(简化):

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 来 create/dispose ServerSideInit() 中的范围,因此我们在控制器外部获得范围内的 DbContext。但这没关系。)

但是 我读到 ASP.NET 核心框架在结束 POST 请求时存在关闭任务的风险! () The simple HostingEnvironment.QueueBackgroundWorkItem isn’t available any longer. There is a new BackgroundService class, though. (https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio) 但是注册服务和排队作业似乎是一个非常麻烦的解决方案……我们只想触发一个需要几秒钟才能完成的任务,并让它在 运行 之后继续 ASP.NET 核心已完成处理 POST 请求。

我对 ASP.NET Core 不是很有经验……所以如果能提供一些意见,我将不胜感激!我的简单解决方案不起作用吗?任务会被框架终止吗?有没有更简单的方法告诉框架“请不要碰这个任务”?或者 BackgroundService 是可行的方法吗?

Doing the server work in the login request (before redirecting to the main page) would slow down the login. “Oh, let’s use a Task then!”, I thought. That is, put the server work in a Task, save that in the user state, and let it execute in parallel with the loading of the main page.

因此,您需要进行请求外工作。也就是说,您的服务器所做的工作 请求范围之外。

您需要问自己的第一个问题是“这项工作需要完成吗?”换句话说,“我可以偶尔失去工作吗?”。如果出于正确性原因必须完成这项工作,那么只有一个真正的解决方案:asynchronous messaging。如果您可以接受偶尔丢失工作(例如,如果主页会检测到 ServerSideInit 未完成并且会在那时完成),那么您真正在谈论的是缓存,并且有一个内存解决方案很好。

But then I read that there is a risk the ASP.NET Core framework shuts down the Task when wrapping up the POST request!

首先要认识到的是shutdowns are normal。在常规部署期间滚动更新、OS 补丁等...您的 Web 服务器 迟早会自动关闭,并且任何假定它将 运行 的代码forever 本质上是错误的。

ASP.NET 默认情况下,当所有请求都得到响应时,Core 将认为自己“可以安全关闭”。这是任何 HTTP 服务的合理行为,并且此逻辑扩展到每个 HTTP 框架,无论语言或 运行time。但是,这显然是请求外部代码的问题。

因此,如果您的代码只是通过直接调用方法(或通过 Task.Run,另一个不幸流行的选项)来启动任务,那么它就处于危险之中:ASP.NET 不知道该请求- 外部代码甚至存在,并且会在请求时愉快地退出,突然终止该代码。

有一些权宜之计,例如 HostingEnvironment.QueueBackgroundWorkItem(预核心)和 IHostedService / IHostApplicationLifetime(核心)。这些注册请求外部代码,以便 ASP.NET 知道它,并且在该代码完成之前不会关闭。但是,这些解决方案只走了一半。因为它们在内存中,所以它们也很危险:ASP.NET 现在知道请求外部代码,但 HTTP 代理、负载平衡器和部署脚本不知道。

Is there some easier way to tell the framework “please don’t touch this Task”?

回到本回答开头的问题:“这项工作需要完成吗?”

如果这只是一个优化并且不需要完成,那么就用Task.Run(或IHostedService)开始工作应该足够了。不过,我不会将它保留在 UserState 中,因为 Task 不可序列化。

如果工作需要完成,那么构建一个asynchronous messaging解决方案。