如果没有应用程序回收,在 ASP.NET 中使用后台工作人员有啥好处?

Posted

技术标签:

【中文标题】如果没有应用程序回收,在 ASP.NET 中使用后台工作人员有啥好处?【英文标题】:Are the benefits to using background workers in ASP.NET if there isn't app recycling?如果没有应用程序回收,在 ASP.NET 中使用后台工作人员有什么好处? 【发布时间】:2020-07-17 21:04:32 【问题描述】:

背景:我有一个简单的 ASP.NET Core 3.1 站点。在极少数情况下(每周三到四次),用户可能会填写触发电子邮件发送的表单。

我不想在运行“发送电子邮件”操作时延迟页面响应(即使它只需要一两秒钟),所以从我读过的所有内容来看,似乎应该处理的代码email 应该是background worker/hosted service,并且 Razor 页面代码应该将要发送的数据对象放在由后台服务监控的集合中。

我不完全理解的是为什么这在 现代 ASP.NET Core 中是必要的。

如果我在普通的 C# 应用程序(不是 ASP)中执行此操作,我只需将“发送电子邮件”方法设为异步(它使用具有异步方法的 MailKit),并在不等待的情况下调用异步方法,从而允许在允许响应线程继续的同时在线程池上完成工作。

但是现有的答案和博客文章说,由于 IIS 可以重新启动 ASP 进程(应用程序池回收),因此在 ASP is dangerous 中调用异步方法而无需等待。

然而,我读过的大多数内容都说应用程序回收是旧 ASP 的产物,当时内存泄漏很常见,而在 .Net Core 上并不是真正的东西。此外,许多 ASP 应用程序甚至不再托管在 IIS 中。

此外,据我所知,IHostedService/Background Worker 对象并没有做任何特别的事情——它们似乎没有添加任何额外的线程;它们看起来就像单例,有额外的环境启动和关闭通知。

所以:

在 ASP.NET Core 中调用即发即弃异步方法是否仍被认为是不好的做法,尤其是在即发即忘任务短暂存在的情况下?如果是这样,为什么? [请参阅下面的编辑以进行说明] 除了关闭通知之外,还有什么理由认为后台服务比借用托管线程池线程(通过 Task.Run 或 QueueBackgroundWorkItem)更好?唤醒后台服务(如果它正在等待将对象放入集合中)不会以相同的方式消耗池线程吗?

编辑:我承认,当有可能终止操作时,启动任务并向用户报告成功是一种糟糕的形式。收到关闭通知并能够完成任务是有好处的。

也许更好的问题是,在现代 ASP(在 IIS 或 Kestrel 上)中是否仍然存在循环的旧行为?是否有其他原因可能会触发有序关闭(服务器关闭/手动停止除外)?

【问题讨论】:

您是否打算再次部署您的应用程序(例如更改代码)?如果是这样,你有你的答案。 这仍然是不好的做法,因为您不能轻易地通过该方法抛出异常。不幸的是,如果将使用 IIS,它仍然可以退出您的线程并在其中运行方法。 您没有提到空闲关机,只是应用程序池回收,这使得这个问题在场景覆盖方面不太完整。此外,工作进程可能会在未处理的异常(来自或不来自那些后台任务)下崩溃。 【参考方案1】:

我仍然认为这是一种糟糕的做法。

这里以及引用的帖子中的主要关注点主要是关于任务完成的承诺。

在不知道幽灵后台任务的情况下,运行时将无法通知任务正常停止。这可能会也可能不会导致严重问题,具体取决于终止发生时的任务状态。

使用 fire forget 任务通常意味着,当进程重新启动时,您的任务有不得不重新开始的风险。有时由于上下文丢失,这是不可能的。想象一下,您的 Fire-forget 任务正在使用 Web 请求提供的参数调用另一个 Web API。如果进程重新启动,参数可能会从内存中清除。

请记住,回收并不总是由 IIS/服务器触发。它也可能由人触发。假设您的应用程序遇到内存泄漏问题,并且您可能希望每 1 小时回收一次应用程序进程作为临时缓解措施。然后你需要确保你不会破坏你的后台任务。

在托管方面 - 仍然可以托管 ASP.Net Core 应用程序in-process,其中应用程序池在配置的时间段或默认 29 小时后由 IIS 回收.

就生命周期而言 - 托管服务是您注册到 DI 的类型,因此可以使用 DI 功能,例如,this built-in hosted service 实现 IDisposable,这意味着可以进行适当的清理关闭时。

坦率地说,后台任务和托管服务都可以让您一劳永逸。但是,当您需要可靠性和弹性时,托管服务会胜出。

【讨论】:

OK - 因此在进程内托管时 IIS 中仍然存在循环。这回答了前半部分(手动循环的例子是一个很好的例子)。但是,对于问题的后半部分,除了通知之外,托管服务是否在做任何特殊的事情(例如,如果我在工作线程中侦听应用程序生命周期事件并对其做出反应,那实际上是否相同,或者托管服务的管理方式不同)? 我不认为主机会为托管服务做些什么特别的事情,你可以在这里查看源代码:github.com/dotnet/aspnetcore/blob/master/src/Hosting/Hosting/… 如果你监听生命周期事件等并相应地处理你的任务,它几乎是在自己构建另一个托管服务框架,为什么不直接使用现有代码:) 这是一个很好的观点——那么我会考虑这个问题。谢谢!【参考方案2】:

为了回答您问题的后半部分,该应用将等待所有托管服务的 StopAsync 方法完成,然后再关闭。只要您在托管服务中await 您的Tasks,这实际上意味着您可以假设您的Tasks 将被允许在应用程序关闭之前完成运行。该应用程序仍可能被强制关闭,在这种情况下,不再有任何保证。

如果您需要更多关于后台任务的保证,您应该将它们移动到单独的进程中运行。您可以使用 Runly 之类的东西,以便更轻松地将功能分解到后台作业中。它还使provide real-time feedback to the user 变得容易,这样当您在后台运行时说“一切都已完成”时,您不会对用户撒谎。

全面披露:我共同创立了 Runly。

【讨论】:

以上是关于如果没有应用程序回收,在 ASP.NET 中使用后台工作人员有啥好处?的主要内容,如果未能解决你的问题,请参考以下文章

asp.net 在调试模式下,socket服务不会停止,但部署到IIS后,过段时间socket服务会停止,而且没有异常

用于 Asp.Net Core 的 Kestrel 网络服务器 - 是不是在一段时间后回收/重新加载

IIS 7 应用程序池自动回收关闭的解决方案

asp.net core定时任务保持存活,不会被回收。asp.net core回收事件

asp.net网站 如果设置iis连接池强制回收,会不会影响网站计数器访问量变化,因为我们网站的访问量总是变化

如何清除asp.net中的服务器缓存?