Controlled Background Services

Controlled Background Services


Overview

BackgroundService is an awesome version of IHostedService in .NET. You can run many of them in the background while your main service (WebHost for instance) is doing its own job.

Each background service creates its own isolated task without interfering the others. Despite that, you can still share code in between and inject services to them.

What's the problem?

Background services get started during the host startup and they teardown when the host teardowns. They take the opportunity to start and stop them at runtime.

Say you have a job within an application that you want to be run once a day while the application is running 24/7. Or imagine you want to start a long running task only when an event triggered. How do we start and stop our background service?

ControlledBackgroundService

This is a wrapper around BackgroundService that gives the opportunity of taking control of start and stop of the service.

Components

IBackgroundServiceController

public interface IBackgroundServiceController
{
    Task PerformStoppingAsync();
    Task PerformStartingAsync();
}        

This interface decorate a background service to have some extra operational method. Note that a background service has already two methods StartAsync() and StopAsync(). However they are not quite there for operation purposes, but for monitoring reasons.

ControllableBackgroundService

public abstract class ControllableBackgroundService : 
        BackgroundService, 
        IBackgroundServiceController
{
    private CancellationTokenSource _tokenSource = new();

    public async Task PerformStoppingAsync()
    {
        await _tokenSource.CancelAsync();
        await StopAsync(CancellationToken.None);
    }

    public async Task PerformStartingAsync()
    {
        if (!_tokenSource.IsCancellationRequested)
            return;

        _tokenSource = 
                CancellationTokenSource.CreateLinkedTokenSource(
                       new CancellationToken()
                );

        await StartAsync(_tokenSource.Token);
    }

    public override Task StartAsync(CancellationToken cancellationToken)
    {
        _tokenSource = 
                CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);

        return base.StartAsync(cancellationToken);
    }

    public override Task StopAsync(CancellationToken cancellationToken)
    {
        _tokenSource.CancelAsync();
        var stopAsync = base.StopAsync(cancellationToken);
        return stopAsync;
    }
}        

Extensions

The following extension simplifies registering a few classes in one line.

public static class Extensions
{
    public static void AddControlledBackgroundService<TService, TImplementation>
            (this IServiceCollection services) 
        where TImplementation : ControllableBackgroundService, TService
        where TService : class, IBackgroundServiceController
    {
        services.AddSingleton<TImplementation>();
        services.AddSingleton<TService>(sp => 
            sp.GetRequiredService<TImplementation>());
            services.AddHostedService(sp => sp.GetRequiredService<TImplementation>()
        );
    }
}        

How to use

Create a new ControlledBackgroundService

Instead of inheriting from BackgroundService, do it from ControllableBackgroundService.

Also adding an interface will help to inject the service to other services.

public interface IMyBackgroundService : IBackgroundServiceController;

public class MyBackgroundService(ILogger logger) : 
    ControllableBackgroundService, 
    IMyBackgroundService
{
    private int _counter;
    private readonly ILogger _logger = logger.ForContext<BackgroundService1>();

    protected override async Task ExecuteAsync(CancellationToken cancellationToken)
    {
        // implement your long run task here
    }
}        

Register the service in Ioc

Here is where we use our extension method and the background service interface.

builder.Services.AddControlledBackgroundService
    <IMyBackgroundService, MyBackgroundService>();        

Inject in a service class

Here is an example of using the service in a WebAPI controller by calling /start and /stop

[ApiController]
[Route("api/[controller]")]
public class OperationController (
    ILogger logger, 
    IMyBackgroundService serviceController
) : ControllerBase
{
    [HttpPost("start")]
    public async Task<IActionResult> GetStart()
    {
        await serviceController.PerformStartingAsync();

        return Ok();
    }

    [HttpPost("stop")]
    public async Task<IActionResult> GetStop()
    {
        await serviceController.PerformStoppingAsync();

        return Ok();
    }
}        

Calling the endpoints

### 
GET http://localhost:5000/api/operation/stop

### 
GET http://localhost:5000/api/operation/start
        

I hope it helps :)



To view or add a comment, sign in

More articles by Dan B. Daneshgar

  • Copilot — Use Wildly / Use Wisely

    Purpose Encourage broad, fearless daily use of GitHub Copilot while creating an intentional feedback loop so value and…

    1 Comment
  • Normalize and Denormalize .NET Core Configuration JSON

    .NET Core ConfigurationBuilder, simply combines all configuration sources introduced to it and creates a flattened…

  • File Base Configuration

    Overview Although it is not the best to store configuration in the file system, however, you might get to the point…

  • Code Review - Architectural and Patterns

    Overview When it is the time to approve a change request (Pull Request / Merge Request) the following questions come to…

    1 Comment
  • Configure Better

    Overview Application configuration service is responsible to bootstrap all sort of configuration from different sources…

  • Log Better

    Overview Logging is one of the main pillars of an application development and application observatory. Although…

Explore content categories