【问题标题】:Letting a task complete while redirecting?在重定向时让任务完成?
【发布时间】:2021-05-22 08:30:17
【问题描述】:

我们正在开发一个单一的 Web 应用程序——非常有状态。它处理 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(和任务)将在超时后被销毁——这完全没问题。 (有一些注意事项。例如,我们必须使用 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 是要走的路?

【问题讨论】:

  • 该任务是静态的还是动态的?意思是,它对所有用户都是一样的还是不同的?
  • 一旦您重定向您返回 HTTP 响应,服务器将停止处理。所以你的所作所为没有多大意义。如果你想要“一劳永逸”,你应该使用Task.Run() 来生成一个新线程,而不是async
  • “并行执行”异步和并行是不同的东西
  • 这是一篇关于这个主题的必读文章:Fire and Forget on ASP.NET

标签: c# asp.net asp.net-core async-await task-parallel-library


【解决方案1】:

在登录请求中执行服务器工作(在重定向到主页之前)会减慢登录速度。 “哦,那我们就用任务吧!”,我想。也就是把服务端工作放到一个Task中,保存在用户态,让它和主页面的加载并行执行。

因此,您需要进行请求外在的工作。即,您的服务器所做的工作在请求范围之外

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

但后来我读到,ASP.NET Core 框架在结束 POST 请求时存在关闭任务的风险!

首先要识别的是shutdowns are normal。在常规部署、操作系统补丁等期间滚动更新...您的网络服务器迟早会自动关闭,任何假设它会永远运行的代码本质上都是错误的。

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

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

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

有没有更简单的方法告诉框架“请不要碰这个任务”?

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

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

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

【讨论】:

  • 感谢您的友好和广泛的回答! :-) 是的,我在发布我的问题之前阅读了 IHostedService 等。这只是一个优化。首先,我有一个登录 HTTP POST 请求,然后我们的 SPA 通过 SignalR 连接。 SPA 页面需要一些时间来加载,而且我们还必须进行一些其他耗时的设置——通过在 POST 请求中启动一个任务并在 SignalR OnConnectedAsync 中等待,这两件事可以并行完成。但是我发布的链接...
  • ... 让我不确定 - 它说 SynchronizationContext 会跟踪任务并在控制器操作完成时引发异常。但这似乎不适用于 Core - 我现在有时间测试它,它工作正常! :-)
猜你喜欢
  • 1970-01-01
  • 2013-07-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-19
  • 2020-07-09
  • 2018-08-19
  • 2023-03-07
相关资源
最近更新 更多