IHostedService实现接口

当需要超出提供的 BackgroundService有限控制时,可以实现自己的 IHostedService控制。 接口 IHostedService 是 .NET 中所有长时间运行的服务的基础。 自定义实现通过 AddHostedService<THostedService>(IServiceCollection) 扩展方法进行注册。

本教程中,您将学习如何:

小窍门

所有“Workers in .NET”示例源代码都可以在样例代码浏览器下载。 有关详细信息,请参阅 “浏览代码示例:.NET 中的 Workers”

先决条件

创建新项目

若要使用 Visual Studio 创建新的辅助角色服务项目,请选择“ 文件>新建>项目...”。从“ 创建新项目 ”对话框搜索“辅助角色服务”,然后选择“辅助角色服务”模板。 如果想要使用 .NET CLI,请在工作目录中打开你喜欢的终端。 运行dotnet new命令,并将<Project.Name>替换为您想要的项目名称。

dotnet new worker --name <Project.Name>

有关 .NET CLI 新辅助角色服务项目命令的详细信息,请参阅 dotnet new worker

小窍门

如果使用 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;
    }
}

重要

WorkerBackgroundService 的子类。 现在,TimerService 实现了IHostedService接口和IAsyncDisposable接口。

TimerServicesealed,并从其_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 运行命令的详细信息,请参阅 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 以发出取消信号。

另请参阅

有几个相关的教程需要考虑: