如何在 ASP.net 核心中手动取消 BackgroundService

How to cancel manually a BackgroundService in ASP.net core

我这样创建一个 BackgroundService:

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

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

如何手动取消?

我检查了 BackgroundService source code

看来我之前的回答是错误的。

ExecuteAsync 参数是 StartAsync 方法提供的标记。

BackgroundService 令牌源已被 StopAsync 方法取消。

因此,要取消 CustomService 异步工作,您必须调用 StopAsync 方法。此取消令牌作为参数提供给 ExecuteAsync 方法。

不清楚您是要取消所有服务和应用程序本身(或至少是主机),还是只取消一个服务。

正在停止应用程序

要取消应用程序,请在需要时注入 IHostApplicationLifetime interface in the class that will force the cancellation and call StopApplication。如果你想从后台服务本身内部取消,也许是因为没有别的事可做,那就是你需要注入的地方。

StopApplication 将告诉主机应用程序需要关闭。主持人将在所有托管服务上调用 StopAsync。由于您使用 BackgroundServicethe implementation 将触发传递给 ExecuteAsynccancellationToken

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

    }

您根本不必更改当前代码。唯一担心的是 await Task.Delay() 泄漏计时器。最好明确地使用 Timer,并在触发取消时处理它。

例如,如果您想通过控制器操作关闭应用程序:

public class MyServiceControllerr:Controller
{
    IHostApplicationLifetime _lifetime;
    public MyServiceController(IHostApplicationLifetime lifeTime)
    {
        _lifeTime=lifeTime;
    }

    [HttpPost]
    public IActionResult Stop()
    {
        _lifeTime.StopApplication();
        return Ok();
    }
}

正在停止服务

如果您只想停止这项服务,您需要一种从其他代码调用其 StopAsync 方法的方法。有很多方法可以做到这一点。一种这样的方法是将 CustomService 注入调用者并调用 StopAsync。但这不是一个好主意,因为它公开了服务并将 controller/stopping 代码与服务耦合。测试这个也不容易。

另一种可能性是为调用 StopAsync 创建一个接口,例如:

public interface ICustomServiceStopper
{
    Task StopAsync(CancellationToken token=default);
}

public class CustomService : BackgroundService,ICustomServiceStopper
{
    ...

    Task ICustomServiceStopper.StopAsync(CancellationToken token=default)=>base.StopAsync(token);
    
}

将接口注册为单例:

services.AddSingleton<ICustomServiceStopper,CustomService>();

并在需要时注入 ICustomServiceStopper

public class MyServiceControllerr:Controller
{
    ICustomServiceStopper _stopper;
    public MyServiceController(ICustomServiceStopper stopper)
    {
        _stopper=stopper;
    }

    [HttpPost]
    public async Task<IActionResult> Stop()
    {
        await _stopper.StopAsync();
        return Ok();
    }
}