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

r1zhe5dt  于 2023-11-20  发布在  .NET
关注(0)|答案(3)|浏览(153)

下面是运行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()运行时创建了一个前台线程,那么我可以理解,因为所有的前台线程都需要在应用程序停止之前完成。
附注:
我可以传递CancellationToken来停止while循环,我知道我可以这样做,在while循环中,我检查令牌是否可以调用等等。
但是我不明白为什么我必须这样做,因为运行的线程是后台线程,而不是前台线程,所以为什么所有后台线程都需要首先完成StopAsync()才能被调用?一个运行的后台线程如何停止exection流到达StopAsync()

pkln4tw6

pkln4tw61#

如果你想停止一个任务,尝试使用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;
    }
}

字符串

to94eoyn

to94eoyn2#

Ctrl+C不会直接终止控制台应用程序的原因,就像你可能习惯于从“普通”控制台应用程序一样,是因为.NET实际上支持阻止Ctrl+C终止应用程序,并让应用程序对Ctrl+C做出React并“做些事情”。
您使用的宿主框架使用了这个系统,一个事件,以防止您的应用程序被强制终止以及接管关闭过程。
事件本身是Console.CancelKeyPress,这是一个 * 可取消的事件 *,意味着您可以从事件处理程序返回,并在EventArgs对象上设置一个标志,以向调用事件处理程序的代码发出信号,表明您希望退出Ctrl+C的默认处理。
托管框架已经做到了这一点。
你可以在这里看到确切的代码:Microsoft. Extensions. Hosting/Internal/ConsoleLifetime.cs@48 -52:

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

字符串
除了取消正常的关闭过程之外,托管框架提供的事件处理程序还取消了传递给StartAsync的令牌CancellationToken。正如您所说,如果您将此令牌传递给任务并对其被取消做出React,则应用程序将关闭。
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时,你的进程永远不会终止。

zmeyuzjn

zmeyuzjn3#

不确定这是否涵盖了你想要实现的100%,但是不要实现IHostedService,尝试从BackgroundService继承,然后重写StartAsyncStopAsyncExecuteAsync,如下所示:

internal class Program
{
    private static void Main(string[] args)
    {
        var builder = Host.CreateApplicationBuilder(args);
        builder.Services.AddHostedService<MyService>();

        var host = builder.Build();
        host.Run();
    }
}

public class MyService : BackgroundService
{
    public override Task StartAsync(CancellationToken cancellationToken)
    {
        Console.WriteLine("Starting . . .");

        return base.StartAsync(cancellationToken);
    }

    public override Task StopAsync(CancellationToken cancellationToken)
    {
        Console.WriteLine("Stopping . . .");

        return base.StopAsync(cancellationToken);
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            Console.WriteLine("Worker running at: {0}", DateTimeOffset.Now);

            await Task.Delay(1000, stoppingToken);
        }
    }
}

字符串
基本上,您将利用BackgroundService的start/stop实现,这与Mugurel在他的回答中尝试实现的类似,但Microsoft的实现实际上是有效的:)
你也可以看看它们的实现,只是为了获得灵感。

相关问题