Search code examples
c#asp.net-corebackground-servicecancellation-token

How to cancel manually a BackgroundService in ASP.net core


I create a BackgroundService like this:

public class CustomService : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken cancellationToken)
    {
        do
        {
            //...

            await Task.Delay(60000, cancellationToken);
        }
        while (!cancellationToken.IsCancellationRequested);
    }
}

How to cancel it manually?


Solution

  • It's unclear whether you want to cancel all services and maybe the application itself (or at least the host), or just a single service.

    Stopping the application

    To cancel the application, inject the IHostApplicationLifetime interface in the class that will force the cancellation and call StopApplication when needed. If you want to cancel from inside the background service itself, perhaps because there's nothing else to do, that's where you need to inject.

    StopApplication will tell the host the application needs to shut down. The host will call StopAsync on all hosted services. Since you use BackgroundService, the implementation will trigger the cancellationToken passed to ExecuteAsync:

    public virtual async Task StopAsync(CancellationToken cancellationToken)
    {
        // Stop called without start
        if (_executeTask == null)
        {
            return;
        }
    
        try
        {
            // Signal cancellation to the executing method
            _stoppingCts.Cancel();
        }
        finally
        {
            // Wait until the task completes or the stop token triggers
            await Task.WhenAny(_executeTask, Task.Delay(Timeout.Infinite, cancellationToken)).ConfigureAwait(false);
        }
    
    }
    

    You don't have to change your current code at all. The only concern is that await Task.Delay() leaks timers. It would be better to use a Timer explicitly, and dispose it when cancellation is triggered.

    For example, if you want to shut down the application from a controller action:

    public class MyServiceController:Controller
    {
        IHostApplicationLifetime _lifetime;
        public MyServiceController(IHostApplicationLifetime lifeTime)
        {
            _lifeTime=lifeTime;
        }
    
        [HttpPost]
        public IActionResult Stop()
        {
            _lifeTime.StopApplication();
            return Ok();
        }
    }
    

    Stopping the service

    If you want to stop just this one service, you need a way to call its StopAsync method from some other code. There are numerous ways to do this. One such way is to inject CustomService to the caller and call StopAsync. That's not a very good idea though, as it exposes the service and couples the controller/stopping code with the service. Testing this won't be easy either.

    Another possibility is to create an interface just for the call to StopAsync, eg :

    public interface ICustomServiceStopper
    {
        Task StopAsync(CancellationToken token=default);
    }
    
    public class CustomService : BackgroundService,ICustomServiceStopper
    {
        ...
    
        Task ICustomServiceStopper.StopAsync(CancellationToken token=default)=>base.StopAsync(token);
        
    }
    
    

    Register the interface as a singleton:

    services.AddSingleton<ICustomServiceStopper,CustomService>();
    

    and inject ICustomServiceStopper when needed:

    public class MyServiceController:Controller
    {
        ICustomServiceStopper _stopper;
        public MyServiceController(ICustomServiceStopper stopper)
        {
            _stopper=stopper;
        }
    
        [HttpPost]
        public async Task<IActionResult> Stop()
        {
            await _stopper.StopAsync();
            return Ok();
        }
    }