重定向时让任务完成?
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解决方案。
我们正在开发一个单体网络应用程序——非常有状态。它处理 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 请求时存在关闭任务的风险! (
我对 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解决方案。