Worker Services Overview

Worker Services - .NET
Learn how to implement a custom IHostedService and use existing implementations in C#. Discover various worker implementations, templates, and service patterns.
https://learn.microsoft.com/en-us/dotnet/core/extensions/workers
Background tasks with hosted services in ASP.NET Core
Learn how to implement background tasks with hosted services in ASP.NET Core.
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-10.0&tabs=visual-studio


Typically used for background services that do not tend to have UIs. Normally made using BackgroundService which is an implementation of IHostedService.

When creating a worker service, there is typically a template available in your IDE.

dotnet new worker

Note: that when creating a worker service you will need to add a NuGet package SDK ect, please review documentation linked above on how to do this. But really you should be fine with the generic template.

Note: if using docker there are some additional options you must consider.

Template

For more info look at:

using App.WorkerService;

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

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

Defaults

Worker Class

namespace App.WorkerService;

public sealed class Worker(ILogger<Worker> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
            await Task.Delay(1_000, stoppingToken);
        }
    }
}

Life-Cycle Management

BackgroundService inherits from IHostedService.

IHostedService offers two lifecycle methods StartAsync or StopAsync that can respectively handle the start-up and closing of your worker.

In the example below we create a service that gracfully shuts down once it has completed its work load. While this is more than the standard boilerplate its a good starting point for any service that should stop gracefully.

InterpreterWorker.cs

using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

public sealed class InterpreterWorker : IHostedService
{
    private readonly ILogger<InterpreterWorker> _logger;
    private CancellationTokenSource? _cts;
    private Task? _runTask;

    public InterpreterWorker(ILogger<InterpreterWorker> logger)
        => _logger = logger;

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("StartAsync");

        _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
        _runTask = WorkerLoopAsync(_cts.Token);

        return Task.CompletedTask;
    }

    public async Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("StopAsync - requesting shutdown");

        // Ask the worker loop to stop.
        _cts?.Cancel();

        if (_runTask is null)
            return;

        // Wait for the loop to finish, but respect the host's shutdown token.
        try
        {
            await _runTask.WaitAsync(cancellationToken);
            _logger.LogInformation("StopAsync - worker finished cleanly");
        }
        catch (OperationCanceledException)
        {
            _logger.LogWarning("StopAsync - shutdown timeout reached; exiting");
        }
    }

    private async Task WorkerLoopAsync(CancellationToken ct)
    {
        while (!ct.IsCancellationRequested)
        {
            // This is where you'd do "claim next scheduled occurrence" + execute.
            await Task.Delay(1000, ct);
        }

        // This is where you'd flush any final state (if needed).
        _logger.LogInformation("RunAsync - exiting loop");
    }
}

Program.cs

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = Host.CreateApplicationBuilder(args);

builder.Services.Configure<HostOptions>(o =>
{
    o.ShutdownTimeout = TimeSpan.FromSeconds(30); // forced quit timeout
});

builder.Services.AddHostedService<InterpreterWorker>();

await builder.Build().RunAsync();