为什么我可以在有后台线程 运行 时停止 IHostedService?

Why I can stop an IHostedService when therre are background threads running?

下面是我运行 IHostedService

的简单代码
internal class Program {
   public static Task Main(string[] args) {
      var host = new HostBuilder().ConfigureServices((hostcontext, services) => {
         services.AddHostedService<MyService>();
      }).Build();

      host.Run();

      return Task.CompletedTask;
   }
}

public class MyService : IHostedService {

   public Task StartAsync(CancellationToken cancellationToken) {
      return Task.Run(() => {
         while (true) {
            Console.WriteLine("Starting Service");
         }
      });
   }

   public Task StopAsync(CancellationToken cancellationToken) {
      Console.WriteLine("Stopping service");
      return Task.CompletedTask;
   }
}

因此,当我想通过在控制台中按 ctrl + C 来停止服务时,我会看到服务停止并且控制台打印“正在停止服务”。

但是当我按下 Ctrl + C 时,服务继续 运行 无限循环,我不明白。

我认为Task.Run()在线程池上排队一个工作项,然后线程池中的一个后台线程来接这个工作,所以在我的例子中,它是一个工作线程(工作线程的id是4 ,主线程的 id 是 1) 执行 while 循环。所以当我按 ctrl + S 时,服务应该停止,那么为什么 运行 后台线程停止服务被停止,不是当应用程序终端时,所有后台作业/线程也终止了吗?我的意思是,如果 Task.Run() 运行创建了一个前台线程,那么我可以理解,因为所有前台线程都需要在应用程序停止之前完成。

P.S:

我可以通过 CancellationToken 来停止 while 循环,我知道我可以这样做,在 while 循环中,我检查令牌是否被调用等...

但我不明白为什么我必须这样做,因为运行线程是后台线程,而不是前台线程,所以为什么所有后台线程都需要先完成StopAsync() 可以调用吗? 运行 后台线程如何停止执行流到达 StopAsync()

如果您想停止任务,请尝试在 MyServiceClass 中使用取消令牌

这是我的例子:

public class MyService : IHostedService
{
    private CancellationTokenSource _ts;

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _ts = new CancellationTokenSource();
        return Task.Factory.StartNew(() =>
        {
            while (true)
            {
                Console.WriteLine("Starting Service");
                Thread.Sleep(500);
        
                if (_ts.Token.IsCancellationRequested)
                {  
                    Console.WriteLine("Stopping service");
                    break;
                }
            }
        }, _ts.Token);
    }


    public Task StopAsync(CancellationToken cancellationToken)
    {
        _ts.Cancel();
        return Task.CompletedTask;
    }
}

之所以 Ctrl+C 不会像您可能习惯于“普通”控制台应用程序那样完全终止您的控制台应用程序,是因为 .NET 中实际上支持防止 Ctrl+C 终止应用程序,并让应用程序对 Ctrl+C 做出反应并“执行操作”。

您正在使用的托管框架已使用此系统,一个事件,以防止您的应用程序被强制终止并接管关闭程序。

事件本身是 Console.CancelKeyPress,这是一个 可取消事件,这意味着您可以从事件处理程序 return 并在 EventArgs 上设置标志对象向调用您的事件处理程序的代码发出信号,表明您想选择退出 Ctrl+C 的默认处理。

托管框架做到了这一点。

您可以在此处查看确切的代码:Microsoft.Extensions.Hosting/Internal/ConsoleLifetime.cs @48-52:

Console.CancelKeyPress += (sender, e) =>
{
    e.Cancel = true;
    ApplicationLifetime.StopApplication();
};

除了取消正常的关闭程序外,托管框架提供的事件处理程序还取消了 CancellationToken,它是传递给 StartAsync 的令牌。正如您所说,如果您将此令牌传递给您的任务并对它被取消做出反应,您的应用程序将关闭。

ApplicationLifetime.StopApplication 的调用最终在 Microsoft.Extensions.Hosting/Internal/ApplicationLifetime.cs @102-112:

结束
private void ExecuteHandlers(CancellationTokenSource cancel)
{
    // Noop if this is already cancelled
    if (cancel.IsCancellationRequested)
    {
        return;
    }

    // Run the cancellation token callbacks
    cancel.Cancel(throwOnFirstException: false);
}

好了。您的应用程序一直在无限循环中旋转的原因恰恰是因为托管框架阻止了正常关闭,而是让您的任务有机会以有序的方式完成。因为你的任务忽略了这个请求,并且只是保持 运行,当你按下 Ctrl+C 时,你的进程永远不会终止。