Mehdi
Mehdi

Reputation: 1126

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?

Upvotes: 17

Views: 23686

Answers (3)

Panagiotis Kanavos
Panagiotis Kanavos

Reputation: 131189

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();
    }
}

Upvotes: 24

sryv
sryv

Reputation: 49

It's late, but simply trigger StopAsync(CancellationToken cancellationToken) method, which is part of BackgroundService interface, it will only stop your current worker, not the whole app:

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    var stopCounter = 10;
    while (!stoppingToken.IsCancellationRequested)
    {
        _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
        await Task.Delay(1000, stoppingToken);
        stopCounter--;
        if (stopCounter < 1)
        {
            StopAsync(stoppingToken);
        }
    }
}

Upvotes: 4

oleksa
oleksa

Reputation: 4007

I've checked the BackgroundService source code

and it looks like my previous answer was wrong.

The ExecuteAsync parameter is a token provided by the StartAsync method.

The BackgroundService token source is cancelled by the StopAsync method.

So to cancel the CustomService async work you have to call the StopAsync method. This cancel token provided to the ExecuteAsync method as parameter.

Upvotes: 1

Related Questions