- Home
- .NET tutorials
- Hosted service has a major update to its lifecycle events
Hosted service has a major update to its lifecycle events
Published: Tuesday 12 December 2023
A worker service project has seen a major update in .NET 8 with the ability to hook into more lifecycle events for a hosted service.
We'll have a look at how at what these lifecycle events are and how they can be implemented into an existing background service.
Creating a worker service project
When creating a worker service project, it typically creates a background service which is similar to this:
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
if (_logger.IsEnabled(LogLevel.Information))
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
}
await Task.Delay(5000, stoppingToken);
}
}
}
This service inherits the BackgroundService
abstract class which includes an abstract ExecuteAsync
method that we have to override.
C# coding challenges
The ExecuteAsync
method requires a cancellation token in the parameter which we typically would use in a while loop whilst the cancellation has not been requested.
It goes ahead and invokes some logic before delaying the task by a certain amount of time before repeating the task.
This continues until the cancellation of the worker service has been requested.
The IHostedService interface
The BackgroundService
abstract class implements the IHostedService
interface which includes methods for when a hosted service starts and stops.
/// <summary>
/// Defines methods for objects that are managed by the host.
/// </summary>
public interface IHostedService
{
/// <summary>
/// Triggered when the application host is ready to start the service.
/// </summary>
/// <param name="cancellationToken">Indicates that the start process has been aborted.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous Start operation.</returns>
Task StartAsync(CancellationToken cancellationToken);
/// <summary>
/// Triggered when the application host is performing a graceful shutdown.
/// </summary>
/// <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous Stop operation.</returns>
Task StopAsync(CancellationToken cancellationToken);
}
This is better than nothing, but it doesn't give us many lifecycle events and therefore flexibility on what we can do.
Introducing the new IHostedLifecycleService
New for .NET 8, the IHostedLifecycleService
interface makes its appearence. This implements the IHostedService
interface so includes the StartAsync
and StopAsync
implementations.
However, there are some new lifecycle events included:
StartingAsync
- Invoked before theStartAsync
methodStartedAsync
- Invoked after theStartAsync
methodStoppingAsync
- Invoked before theStopAsync
methodStoppedAsync
- Invoked after theStopAsync
method
This is great as we can use these new methods for our worker service such as importing or exporting data and validation.
The problem with BackgroundService
The problem with the existing BackgroundService
abstract class is that it implements the IHostedService
interface, so only takes advantage of the StartAsync
and StopAsync
methods.
As far as we know, there isn't a separate background service abstract class in .NET 8 to accommodate the new lifecycle events, so how do we take advantage of the new lifecycle events?
Adding the IHostedLifecycleService to an existing background service
The answer is to implement the IHostedLifecycleService
to the existing background service and implementing the new methods into it.
This means that we can take advantage of the new methods in our background service.
In this example, we are logging a message for each lifecycle event to confirm the order in which they are triggered.
public class Worker : BackgroundService, IHostedLifecycleService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
public Task StartingAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("** Starting **");
return Task.CompletedTask;
}
public override Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("** Start **");
return base.StartAsync(cancellationToken);
}
public Task StartedAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("** Started **");
return Task.CompletedTask;
}
public Task StoppingAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("** Stopping **");
return Task.CompletedTask;
}
public override Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("** Stop **");
return base.StopAsync(cancellationToken);
}
public Task StoppedAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("** Stopped **");
return Task.CompletedTask;
}
protected override async Task ExecuteAsync(
CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
if (_logger.IsEnabled(LogLevel.Information))
{
_logger.LogInformation(
"Worker running at: {time}",
DateTimeOffset.Now
);
}
await Task.Delay(5000, cancellationToken);
}
}
}
Watch the new lifecycle events in action
Watch our video where we talk through the changes to the hosted service in .NET 8 and demo them to ensure that we can successfully hook into the new lifecycle events and confirm the order in which the events are triggered.