实现 IHostedService
接口
当需要超出提供的 BackgroundService 的有限控制,可实现你自己的 IHostedService。 IHostedService 接口是 .NET 中所有长期运行的服务的基础。 已使用 AddHostedService<THostedService>(IServiceCollection) 扩展方法注册自定义实现。
在本教程中,你将了解如何执行以下操作:
- 实现 IHostedService 和 IAsyncDisposable 接口。
- 创建基于计时器的服务。
- 使用依赖项注入和日志记录注册自定义实现。
提示
所有“.NET 中的辅助角色”示例源代码都可以在示例浏览器中下载。 有关详细信息,请参阅浏览代码示例:.NET 中的辅助角色。
先决条件
- .NET 8.0 SDK 或更高版本
- .NET 集成开发环境 (IDE)
- 随意使用 Visual Studio
创建新项目
若要使用 Visual Studio 创建新的辅助角色服务项目,请选择“文件”“新建”“项目...”。从“创建新项目”对话框搜索“辅助角色服务”,并选择辅助角色服务模板。 如果你想要使用 .NET CLI,请在工作目录中打开你最喜欢的终端。 运行 dotnet new
命令,将 <Project.Name>
替换为所需的项目名称。
dotnet new worker --name <Project.Name>
有关 .NET CLI 新建辅助角色服务项目命令的详细信息,请参阅 dotnet new 辅助角色。
提示
如果使用 Visual Studio Code,则可以从集成终端运行 .NET CLI 命令。 有关详细信息,请参阅 Visual Studio Code:集成终端。
创建计时器服务
基于计时器的后台服务使用 System.Threading.Timer 类。 计时器触发 DoWork
方法。 在 IHostLifetime.StopAsync(CancellationToken) 上禁用计时器,并在 IAsyncDisposable.DisposeAsync() 上处置服务容器时处置计时器:
将模板中 Worker
的内容替换为以下 C# 代码,并将文件重命名为“TimerService.cs”:
namespace App.TimerHostedService;
public sealed class TimerService(ILogger<TimerService> logger) : IHostedService, IAsyncDisposable
{
private readonly Task _completedTask = Task.CompletedTask;
private int _executionCount = 0;
private Timer? _timer;
public Task StartAsync(CancellationToken stoppingToken)
{
logger.LogInformation("{Service} is running.", nameof(TimerHostedService));
_timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
return _completedTask;
}
private void DoWork(object? state)
{
int count = Interlocked.Increment(ref _executionCount);
logger.LogInformation(
"{Service} is working, execution count: {Count:#,0}",
nameof(TimerHostedService),
count);
}
public Task StopAsync(CancellationToken stoppingToken)
{
logger.LogInformation(
"{Service} is stopping.", nameof(TimerHostedService));
_timer?.Change(Timeout.Infinite, 0);
return _completedTask;
}
public async ValueTask DisposeAsync()
{
if (_timer is IAsyncDisposable timer)
{
await timer.DisposeAsync();
}
_timer = null;
}
}
重要
Worker
是 BackgroundService 的子类。 现在,TimerService
可同时实现 IHostedService 和 IAsyncDisposable 接口。
TimerService
为 sealed
,并级联来自其 _timer
实例的 DisposeAsync
调用。 有关“级联释放模式”详细信息,请参阅实现 DisposeAsync
方法。
调用 StartAsync 时,将实例化计时器,从而启动计时器。
提示
Timer 不等待先前的 DoWork
执行完成,因此所介绍的方法可能并不适用于所有场景。 使用 Interlocked.Increment 以原子操作的形式执行计数器递增,这可确保多个线程不会并行更新 _executionCount
。
将现有 Program
内容替换为以下 C# 代码:
using App.TimerHostedService;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<TimerService>();
IHost host = builder.Build();
host.Run();
已使用 AddHostedService
扩展方法在 (Program.cs) 中注册该服务。 这是注册 BackgroundService 子类时使用的相同扩展方法,因为它们均实现 IHostedService 接口。
有关注册服务的详细信息,请参阅 .NET 中的依赖关系注入。
验证服务功能
若要从 Visual Studio 运行应用程序,请选择 F5 或选择“调试”>“开始调试”菜单选项。 如果使用的是 .NET CLI,请从工作目录运行 dotnet run
命令:
dotnet run
有关 .NET CLI run 命令的详细信息,请参阅 dotnet run。
让应用程序运行一段时间,以生成多个执行计数增量。 你将看到与下面类似的输出:
info: App.TimerHostedService.TimerService[0]
TimerHostedService is running.
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: .\timer-service
info: App.TimerHostedService.TimerService[0]
TimerHostedService is working, execution count: 1
info: App.TimerHostedService.TimerService[0]
TimerHostedService is working, execution count: 2
info: App.TimerHostedService.TimerService[0]
TimerHostedService is working, execution count: 3
info: App.TimerHostedService.TimerService[0]
TimerHostedService is working, execution count: 4
info: Microsoft.Hosting.Lifetime[0]
Application is shutting down...
info: App.TimerHostedService.TimerService[0]
TimerHostedService is stopping.
如果从 Visual Studio 内部运行应用程序,请选择“调试”>“停止调试...” 。或者,从控制台窗口中选择“Ctrl” + “C”,以发送取消信号。
另请参阅
需要考虑几个相关教程: