Бөлісу құралы:


Реализация интерфейса IHostedService

Если требуется конечный элемент управления, не предусмотренный классом BackgroundService, можно реализовать собственный интерфейс IHostedService. Интерфейс IHostedService служит основой для всех длительно выполняющихся служб в .NET. Настраиваемые реализации регистрируются с помощью метода расширения AddHostedService<THostedService>(IServiceCollection).

В этом руководстве описано следующее:

  • Реализуйте интерфейсы IHostedService и IAsyncDisposable.
  • Создайте службу на основе таймера.
  • Зарегистрируйте настраиваемую реализацию с помощью внедрения зависимостей и ведения журнала.

Совет

Весь пример исходного кода "Рабочие роли в .NET" доступен для скачивания в Обозревателе примеров. Дополнительные сведения см. в разделе Обзор примеров кода: рабочие роли в .NET.

Необходимые компоненты

Создание нового проекта

Чтобы создать новый проект службы рабочей роли с помощью Visual Studio, выберите Файл>Создать>Проект. В диалоговом окне Создание нового проекта выполните поиск по запросу "служба рабочей роли" и выберите шаблон "Служба рабочей роли". Если вы предпочитаете использовать .NET CLI, откройте используемый терминал в рабочем каталоге. Выполните команду dotnet new и замените <Project.Name> именем проекта.

dotnet new worker --name <Project.Name>

Дополнительные сведения о команде .NET CLI для создания проекта службы рабочей роли см. здесь.

Совет

Если вы используете 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 и выполняет каскадный вызов DisposeAsync из своего экземпляра _timer. Дополнительные сведения о "каскадном шаблоне удаления" см. в статье Реализация метода 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();

Служба зарегистрирована в (Program.cs) с AddHostedService помощью метода расширения. Это тот же метод расширения, который используется при регистрации подклассов 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 в окне консоли, чтобы сообщить об отмене.

См. также

Рекомендуется ознакомиться с рядом учебников на смежные темы: