BackgroundService没有关闭,.net core通用主机中的stoppingToken从未设置

4

我有一个在.net通用主机中托管的BackgroundService,如下所示:

var builder = Host.CreateDefaultBuilder(args);

builder
    .ConfigureLogging((hostingContext, logging) =>
    {
        logging.ClearProviders();
        logging.AddConsole();
        if(hostingContext.HostingEnvironment.IsDevelopment() == true)
            logging.AddDebug();
    })
    .ConfigureHostConfiguration(configurationBuilder =>
    {
        configurationBuilder.AddCommandLine(args);
    })
    .ConfigureAppConfiguration((hostingContext, configApp) =>
    {
        var env = hostingContext.HostingEnvironment;
        Console.WriteLine(env.EnvironmentName);
    })
    .UseConsoleLifetime();

接下来我会定义我的工作线程:

public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;

    public Worker(ILogger<Worker> logger)
    {
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // here i am doing something with ClientWebSocket, threads etc
        // snipped for brevity. I want to avoid spinning in a loop

        // the debugger reaches the following line
        WaitHandle.WaitAny(new[] { stoppingToken.WaitHandle });

        // but never hits this line
        this.logger.LogInformation("shutting down worker");
    }
}

在运行应用程序的 Windows 终端中按下 ctrl+c 时,它会显示“Application is shutting down”(来自框架),但是 stoppingToken 从未被设置(因此我无法关闭我的工作进程)。
停止令牌是何时设置的?如何设置并优雅地终止工作进程?
控制台。

你只需要这样做:while (!stoppingToken.IsCancellationRequested) { await Task.Delay(100, stoppingToken); } - Daniel Lorenz
@DanielLorenz 我已经为代码添加了一些上下文,我有一些后台线程和事件处理程序自行运行... 即使剪切这部分代码,stoppingToken也从未被设置。如果可能的话,我想避免在循环中旋转。 - morleyc
但这正是BackgroundService的全部目的。您需要一遍又一遍地不断执行某些操作。如果您只想执行一次操作,那么就让进程结束,并且不要调用WaitHandle,因为您并不关心它。 - Daniel Lorenz
是的,它通过Web套接字不断接收消息并转发,必须在循环中保持不断的活动状态。我可以在紧密的循环中轮询 stopping token.IsCancellationRequested==true,但我想在一些代码片段的异步函数调用上使用取消标记(将其作为参数传递给CancellationToken)。 - morleyc
1个回答

2

对于后台服务,它们需要一直运行,直到取消令牌指示停止。为了做到这一点,您需要一个 while 循环。然而,当您将此取消令牌传递到任何异步方法中时,如果在所有层中使用相同的令牌,则会停止该方法从头到尾运行。应该像这样:

  protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            // here i am doing something with ClientWebSocket, threads etc
            // snipped for brevity. I want to avoid spinning in a loop
            ... 
            await client.DownloadAsync(..., stoppingToken);
            ...
        }

        // but never hits this line
        this.logger.LogInformation("shutting down worker");
    }

谢谢,伙计。说实话,我最终只是在 while(!stoppingToken.IsCancellationRequested) { await Task.Delay(1000); } 上加了一个紧密的循环。不确定为什么如果我在 stoppingToken 上使用 WaitHandle.WaitAny(new[] { stoppingToken.WaitHandle }); 进行睡眠,它从未被唤醒 - 我是否在线程阻塞方面做错了什么? - morleyc
1
@g18c 我认为它卡在那里是因为令牌的取消请求下去了,但它从未回来,因为你的代码行阻塞了它。至少,这是我的假设。 - Daniel Lorenz

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