ASP.NET Core 中的后台工作者





5.00/5 (3投票s)
如何在请求之外向 ASP.NET Core 应用程序添加功能
引言
在这篇文章中,我们将讨论如何在请求之外向 ASP.NET Core 应用程序添加功能。
这篇文章的代码可以在这里找到。
故事
正如你们中的一些人,甚至所有人所知道的那样,Web 服务器通常只在请求的上下文中工作。因此,当我们部署 ASP.NET Core(或任何其他 Web 服务器)并且它没有收到对服务器请求的响应时,它将一直保持插入在服务器上,等待来自浏览器或 API 端点的请求。
但是,在某些情况下,根据正在构建的应用程序,我们需要在请求的上下文之外执行一些工作。以下是此类可能场景的列表
- 向用户提供通知
- 抓取货币汇率
- 进行数据维护和归档
- 与非确定性外部系统通信
- 处理审批工作流程
虽然 Web 服务器除了响应请求之外不会做更多事情,否则这将是常识,但了解如何在我们的应用程序中嵌入这种行为而不创建 worker 应用程序是有用的。
设置
项目
首先,让我们创建一个 ASP.NET Core 应用程序,在我的例子中,我创建了一个 2.1 MVC 应用程序。
我们将使用此项目创建一个后台工作者作为示例。
可注入的工作者
虽然此步骤对于我们的工作不是强制性的,但我们将创建一个 worker 类,该类将通过注入实例化,以便我们可以测试 worker 类并使其与主应用程序分离。
namespace AspNetBackgroundWorker
{
using Microsoft.Extensions.Logging;
public class BackgroundWorker
{
private readonly ILogger _logger;
private int _counter;
public BackgroundWorker(ILogger logger)
{
_counter = 0;
_logger = logger;
}
public void Execute()
{
_logger.LogDebug(_counter.ToString());
_counter++;
}
}
}
请注意,对于此示例,此类除了记录计数器之外没有做太多事情,但我们使用ILogger
的原因是为了我们可以看到它在创建时以及注入依赖项时的实际效果。
在控制反转容器中注册工作者
在Startup.cs文件中的ConfigureServices
方法中,我们将引入以下行
services.AddSingleton();
它不需要是单例,但它将很好地服务于我们的目的。
实现
现在我们已经创建并注册了一个可测试和可注入的工作者类,我们将继续使其在后台运行。
为此,我们将进入Program.cs文件并将其更改为以下内容
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
namespace AspNetBackgroundWorker
{
using System;
using System.Threading;
using Microsoft.Extensions.DependencyInjection;
public class Program
{
public static void Main(string[] args)
{
// We split up the building of the webHost with running it
// so that we can do some additional work before the server actually starts
var webHost = CreateWebHostBuilder(args).Build();
// We create a dedicated background thread that will be running alongside the web server.
Thread counterBackgroundWorkerThread = new Thread(CounterHandlerAsync)
{
IsBackground = true
};
// We start the background thread, providing it with the webHost.Service
// so that we can benefit from dependency injection.
counterBackgroundWorkerThread.Start(webHost.Services);
webHost.Run(); // At this point, we're running the server as normal.
}
private static void CounterHandlerAsync(object obj)
{
// Here we check that the provided parameter is, in fact, an IServiceProvider
IServiceProvider provider = obj as IServiceProvider
?? throw new ArgumentException
($"Passed in thread parameter was not of type {nameof(IServiceProvider)}", nameof(obj));
// Using an infinite loop for this demonstration but it all depends
// on the work you want to do.
while (true)
{
// Here we create a new scope for the IServiceProvider
// so that we can get already built objects from the Inversion Of Control Container.
using (IServiceScope scope = provider.CreateScope())
{
// Here we retrieve the singleton instance of the BackgroundWorker.
BackgroundWorker backgroundWorker = scope.ServiceProvider.GetRequiredService();
// And we execute it, which will log out a number to the console
backgroundWorker.Execute();
}
// This is only placed here so that the console doesn't get spammed
// with too many log lines
Thread.Sleep(TimeSpan.FromSeconds(1));
}
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup();
}
}
我提供了一些内联注释,以便更容易理解。
要测试此代码,我们需要在控制台/项目模式下运行应用程序,以便我们可以在控制台窗口上进行跟踪。
结论
虽然此示例在实际场景中没有做太多事情,但它确实向我们展示了如何创建后台线程并使其与 Web 服务器一起运行。
此外,不一定需要从Program.cs文件运行线程,但由于这将是一个后台工作者,它将永远做它的事情,我认为这是一个不错的地方。可以使用它的其他一些地方是
- 来自中间件
- 来自控制器
- 创建一个可以接收方法和委托来运行临时和任意方法的类。
并且由于我们正在使用IServiceProvider
,我们可以使用所有已注册的服务,不仅是我们注册的服务,还有 Web 服务器注册的服务,例如Logger
,Options
,DbContext
。
我个人在一个 signalR 集线器会定期向特定用户发送通知的场景中使用它,这需要在 Web 请求的上下文之外运行。
我希望你喜欢这篇文章并发现它有用。