如何使用.NET 6的IHostedService和BackgroundService?
大家好,我是张飞洪,感谢您的阅读,我会不定期和你分享学习心得,希望我的文章能成为你成长路上的垫脚石,让我们一起精进。
本章是《定制ASP NET 6.0框架系列文章》的第七篇。本文内容和定制无关,主要是关于创建后台服务,用于异步运行任务的功能,我们会使用此功能定期从远程服务获取数据。
本文的主题主要包括:
IHostedService
介绍BackgroundService
介绍Worker Service
介绍
1 技术要求
为了演示,我们先创建一个ASP.NET Core
应用,我们使用控制台(Shell
或Bash
)终端,切换到工作目录,执行以下命令,创建一个MVC应用程序:
dotnet new mvc -n HostedServiceSample -o HostedServiceSample
使用Visual Studio双击打开项目文件,你也可以使用VS Code打开项目,并在已打开的控制台中执行以下命令:
cd HostedServiceSample code .
2 IHostedService
介绍
托管服务自ASP.NET Core 2.0
开始出现,可在应用后台异步运行任务。比如,可以定期获取数据、在后台进行一些计算或进行一些数据清理。甚至,我们还可以使它发送预配置的电子邮件,或者在后台执行任意的逻辑操作。
托管服务必须实现IHostedService
接口,如下代码所示:
public class SampleHostedService : IHostedService {
public Task StartAsync(CancellationToken cancellationToken) { }
public Task StopAsync(CancellationToken cancellationToken) { }
}
IHostedService
需要实现StartAsync()
和StopAsync()
方法。StartAsync()
是实现要执行的逻辑的地方,该方法在应用程序启动后立即执行,并且只执行一次;StopAsync()
方法在应用程序停止之前执行。这意味着要我们需要自己实现一个计划服务,实现一个定期执行代码的循环。
要执行IHostedService
,还需要在ASP.NET Core
依赖注入容器注册作为单例实例:
builder.Services.AddSingleton<IHostedService, SampleHostedService>();
我们再看下面这个示例,用于展示托管服务的工作方式。它会在启动、停止和每2秒向控制台写入一条日志消息:
首先,我们编写类的骨架,并通过DI反转一个ILogger
的实例:
namespace HostedServiceSample;
public class SampleHostedService:IHostedService {
private readonly ILogger<SampleHostedService> logger;
public SampleHostedService(ILogger<SampleHostedService> logger) {
this.logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{}
public Task StopAsync(CancellationToken cancellationToken)
{}
}
下一步,实现StopAsync
方法。此方法用于需要关闭连接时需要处理的逻辑工作:
public Task StopAsync(CancellationToken cancellationToken)
{
logger.LogInformation("托管服务停止……");
return Task.CompletedTask;
}
实际工作将在StartAsync
方法内完成:
public Task StartAsync(CancellationToken cancellationToken) {
logger.LogInformation("托管服务开始……");
return Task.Factory.StartNew(async () => {
while (!cancellationToken.IsCancellationRequested) {
logger.LogInformation($"托管服务执行中 - {DateTime.Now}");
try{
//等待2秒
await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken);
}catch (OperationCanceledException) { }
}
}, cancellationToken);
}
接下来运行并测试一下,效果如下图所示,日志每2秒写入一次控制台。:
dotnet run
3 BackgroundService
介绍
BackgroundService
类是在ASP.NET Core 3.0
中被引入的,它是一个实现IHostedService
接口的抽象类,它还提供了一个名为ExecuteAsync
的抽象方法,该方法返回一个Task
任务。
我们重写一下上面示例的托管服务,先搭建一个骨架类,如下所示:
namespace HostedServiceSample;
public class SampleBackgroundService : BackgroundService {
private readonly ILogger<SampleHostedService> logger;
public SampleBackgroundService(ILogger<SampleHostedService> logger){
this.logger = logger;
}
}
接下来重写StopAsync
方法:
public override async Task StopAsync(CancellationToken cancellationToken) {
logger.LogInformation("后台服务停止……");
await Task.CompletedTask;
}
最后,我们重写ExecuteAsync
方法:
protected override async Task ExecuteAsync(CancellationToken cancellationToken) {
logger.LogInformation("后台服务启动……");
await Task.Factory.StartNew(async () =>{
while(!cancellationToken.IsCancellationRequested) {
logger.LogInformation($"后台服务执行中 - {DateTime.Now}");
try{
await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken);
}catch (OperationCanceledException) {}
}
}, cancellationToken);
}
注册也要重写一下,在ASP.NET Core 3.0
及更高版本中,ServiceCollection
有一个新的扩展方法来注册托管服务或后台工作程序:
builder.Services.AddHostedService<SampleBackgroundService>();
在控制台中调用以下命令启动应用程序测试:
dotnet run
测试结果和上头一样,这里不再累述。
4 Worker Service
介绍
Worker Service
可以叫执行者或者工作者。
在ASP.NET Core 3.0
及更高版本中,创建简单的工作者服务变得非常容易,这个服务可以也托管在非Web服务器里。
我们再新建一个项目:
dotnet new worker -n BackgroundServiceSample -o BackgroundServiceSample
我们可以看到,该命令会创建一个带有Program.cs
和Worker.cs
的控制台应用程序。Worker.cs
包含Worker
类,它是从BackgroundService
类继承的。在ASP.NET 5.0
及更早版本,Program.cs
文件看起来与以前版本的很相似,但没WebHostBuilder
:
public class Program {
public static void Main(string[] args){
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args)=>
Host.CreateDefaultBuilder(args).ConfigureServices((hostContext, services) =>{
services.AddHostedService<Worker>();
});
}
在ASP.NET Core 6.0
中,Program.cs
以与迷你API相同,都被简化掉了。它看起来像这样:
using BackgroundServiceSample;
IHost host = Host.CreateDefaultBuilder(args).ConfigureServices(services =>{
services.AddHostedService<Worker>();
}).Build();
await host.RunAsync();
这里创建IHost
并启用依赖注入,我们可以在任何类型的.NET Core
应用程序中使用依赖注入,而不仅仅是在ASP.NET Core
应用程序。
然后,我们将工作进程添加到服务集合中,这样就可以将服务作为Windows
服务或Docker
容器中的后台程序运行。
小结
有了上面的抛砖引玉,现在您可以使用IHostedService
和BackgroundService
开始做一些更复杂的事情了。
如果后台服务和应用都在同一个进程中运行,会消耗太多的CPU或内存,甚至降低应用的速度。
所以,对于大型的应用程序,建议单独创建一个用于执行后台任务的应用程序中,可以部署在单独的Docker容器里、或者云服务器里。一定要确保它与主应用程序分开。
在下一章中,我们将进入中间件的学习,以及如何使用中间件在请求管道上实现特殊逻辑,感谢您的阅读。