在Web服务器的常规操作中,通常都是在处理客户端请求的上下文中运行。然而,在某些特定情况下,可能需要在请求的上下文之外执行一些工作。例如,向用户发送通知、抓取货币汇率、进行数据维护和归档、与非确定性外部系统通信、处理审批工作流等。虽然这些场景并不常见,但了解如何在不创建工作应用程序的情况下将这种行为嵌入到应用程序中是非常有用的。
首先,将创建一个ASP.NET Core应用程序。以示例为例,创建了一个2.1版本的MVC应用程序。将使用这个项目来创建一个后台工作线程作为示例。
虽然这一步不是必需的,但将创建一个工作线程类,该类将通过注入进行实例化,这样可以测试工作线程类,并将其与主应用程序解耦。
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)
{
var webHost = CreateWebHostBuilder(args).Build();
Thread counterBackgroundWorkerThread = new Thread(CounterHandlerAsync) { IsBackground = true };
counterBackgroundWorkerThread.Start(webHost.Services);
webHost.Run();
}
private static void CounterHandlerAsync(object obj)
{
IServiceProvider provider = obj as IServiceProvider ?? throw new ArgumentException($"Passed in thread parameter was not of type {nameof(IServiceProvider)}", nameof(obj));
while (true)
{
using (IServiceScope scope = provider.CreateScope())
{
BackgroundWorker backgroundWorker = scope.ServiceProvider.GetRequiredService();
backgroundWorker.Execute();
}
Thread.Sleep(TimeSpan.FromSeconds(1));
}
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args).UseStartup();
}
}
提供了一些内联注释,以便更容易理解。
要测试这段代码,需要在控制台/项目模式下运行应用程序,以便可以在控制台窗口中跟踪。
虽然这个示例在现实生活场景中并没有做太多事情,但它确实向展示了如何创建一个后台线程并使其与Web服务器并行运行。
运行线程并不一定要在Program.cs文件中进行,但由于这将是一个永远运行的后台工作线程,认为这将是一个不错的地方。其他可能使用它的地方包括:
由于正在使用IServiceProvider,可以使用所有注册的服务,而不仅仅是注册的那些,还包括Web服务器注册的那些,例如Logger、Options、DbContext。