Implement the IHostedService interface - .NET (original) (raw)

When you need finite control beyond the provided BackgroundService, you can implement your own IHostedService. The IHostedService interface is the basis for all long running services in .NET. Custom implementations are registered with the AddHostedService(IServiceCollection) extension method.

In this tutorial, you learn how to:

Prerequisites

Create a new project

To create a new Worker Service project with Visual Studio, you'd select File > New > Project.... From the Create a new project dialog search for "Worker Service", and select Worker Service template. If you'd rather use the .NET CLI, open your favorite terminal in a working directory. Run the dotnet new command, and replace the <Project.Name> with your desired project name.

dotnet new worker --name <Project.Name>

For more information on the .NET CLI new worker service project command, see dotnet new worker.

Create timer service

The timer-based background service makes use of the System.Threading.Timer class. The timer triggers the DoWork method. The timer is disabled on IHostLifetime.StopAsync(CancellationToken) and disposed when the service container is disposed on IAsyncDisposable.DisposeAsync():

Replace the contents of the Worker from the template with the following C# code, and rename the file to 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;
    }
}

The TimerService is sealed, and cascades the DisposeAsync call from its _timer instance. For more information on the "cascading dispose pattern", see Implement a DisposeAsync method.

When StartAsync is called, the timer is instantiated, thus starting the timer.

Tip

The Timer doesn't wait for previous executions of DoWork to finish, so the approach shown might not be suitable for every scenario. Interlocked.Increment is used to increment the execution counter as an atomic operation, which ensures that multiple threads don't update _executionCount concurrently.

Replace the existing Program contents with the following C# code:

using App.TimerHostedService;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<TimerService>();

IHost host = builder.Build();
host.Run();

The service is registered in (Program.cs) with the AddHostedService extension method. This is the same extension method you use when registering BackgroundService subclasses, as they both implement the IHostedService interface.

For more information on registering services, see Dependency injection in .NET.

Verify service functionality

To run the application from Visual Studio, select F5 or select the Debug > Start Debugging menu option. If you're using the .NET CLI, run the dotnet run command from the working directory:

dotnet run

For more information on the .NET CLI run command, see dotnet run.

Let the application run for a bit to generate several execution count increments. You will see output similar to the following:

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.

If running the application from within Visual Studio, select Debug > Stop Debugging.... Alternatively, select Ctrl + C from the console window to signal cancellation.

See also

There are several related tutorials to consider: