如何在ASP.net core中手动取消BackgroundService?

14

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

如何手动取消它?

你可以获取它的引用; .GetService<IEnumerable<IHostedService>>().OfType<CustomService>().Single().MethodName(....)。(或注入其他常见服务以指示取消...) - Jeremy Lakeman
这是我想到的方法,但我想知道是否可以通过 cancellationToken 来完成? - Mehdi
您不需要引用 IHostedServiceBackgroundService 就是 一个 IHostedSevice。但是,您所说的 手动取消 是什么意思呢?是显式地取消所有后台服务,甚至应用程序本身吗?还是只取消一个服务而让其他服务继续运行? - Panagiotis Kanavos
@PanagiotisKanavos 如果您想让其他服务定位到您的 BackgroundService 实例,您需要像上面我的示例那样做一些事情。如果您以另一种方式注册服务,您将拥有多个实例。 - Jeremy Lakeman
3个回答

20

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

停止应用程序

要取消应用程序,请在将强制取消的类中注入 IHostApplicationLifetime 接口,并在需要时调用 StopApplication。如果您想从后台服务本身内部取消,可能是因为没有其他事情可做,那就需要注入。

StopApplication 将告诉主机应用程序需要关闭。主机将调用所有托管服务上的 StopAsync。由于使用了 BackgroundService,因此实现将触发传递给 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。但这不是一个很好的想法,因为它会暴露服务并将控制器/停止代码与服务耦合起来。测试这个也不容易。

另一种可能性是创建一个接口,专门用于调用 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();
    }
}

据我所知,所有的BackgroundService在应用关闭时都会自动关闭,对吧? - Mehdi

4

虽然已经很晚了,但是只需触发StopAsync(CancellationToken cancellationToken)方法即可,该方法是BackgroundService接口的一部分,它只会停止当前的worker,而不会停止整个应用程序:

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

1

我已经检查了BackgroundService源代码

看起来我的先前答案是错误的。

ExecuteAsync参数是由StartAsync方法提供的令牌。

BackgroundService令牌源由StopAsync方法取消。

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


1
OP已经在使用CancellationToken了。问题是如何首先触发取消。 - Panagiotis Kanavos
@PanagiotisKanavos OP使用取消令牌,但正在询问如何取消。这看起来有点奇怪,因此我展示了两个选项如何取消后台服务的继承者。 - oleksa
2
这并不奇怪 - 当在服务工作者中使用 Ctrl+C 时就会发生这种情况。想要显式停止服务也并不奇怪。你发布的内容并没有做你所认为的那样。StopAsync 已经在 BackgroundService 中实现了。它用于响应取消请求,而不是触发它。 - Panagiotis Kanavos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接