Worker Services Overview


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();
- Creates an application builder
- Registers the worker as a hosted service
- Builds an IHost from the builder
- Calls Run on the host
Defaults
- Garbage collection is OFF - you probs want it on so - Review LINK
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();