首页 > 解决方案 > 为什么调用 IHost.StopAsync 时会调用两次 IHostedService.StopAsync?

问题描述

鉴于以下使用通用主机的 .NET Core 2.2 控制台应用程序:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace SimpleGenericHost
{
    class SimpleHostedService : IHostedService
    {
        public Task StartAsync(CancellationToken cancellationToken)
        {
            Console.WriteLine("Service started");
            return Task.CompletedTask;
        }

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

    class Program
    {
        static async Task Main(string[] args)
        {
            var host = new HostBuilder()
                .ConfigureServices(services =>
                {
                    services.AddHostedService<SimpleHostedService>();
                })
                .Build();

            var runTask = host.RunAsync();
            await Task.Delay(5000);
            await host.StopAsync();
            await runTask;

        }
    }
}

当你运行它时,输出如下:

Service started
Application started. Press Ctrl+C to shut down.
Hosting environment: Production
Content root path: C:\projects\ConsoleApps\SimpleGenericHost\bin\Debug\netcoreapp2.2\
Service stopped
Service stopped

如您所见SimpleHostedService.StopAsync,它被调用了两次。为什么?

这是预期的吗?我错过了什么吗?是否有另一种方法可以停止IHostedService.StopAsync只调用一次的主机?

标签: c#.net-core

解决方案


因为它被调用了两次——一次在延迟之后,另一次在服务实际停止时。它并不意味着在使用时被调用RunAsync

要在超时后停止,请使用带有超时的 CancellationTokenSource 并将其令牌传递给RunAsync

var timeoutCts=new CancellationTokenSource(5000);

await host.RunAsync(timeoutCts.Token);

解释

StopAsync 不会停止服务,它用于通知服务它需要停止。当应用程序停止时,它由托管服务基础架构本身调用。

.NET Core 是开源的,这意味着您可以查看RunAsync 源代码RunAsync启动主机,然后等待终止:

await host.StartAsync(token);

await host.WaitForShutdownAsync(token);

WaitForShutdownAsync方法侦听来自控制台或显式调用的终止请求IHostApplicationLifetime.StopApplication。当这种情况发生时,它会调用StopAsync自己:

await waitForStop.Task;

// Host will use its default ShutdownTimeout if none is specified.
await host.StopAsync();

如果您打算自己管理应用程序的生命周期,则应该使用StartAsync而不是。RunAsync

但在这种情况下,您只需要在应用程序超时时停止它。您可以通过CancellationTokenSource(int)构造函数将取消令牌传递给RunAsync仅在超时后触发的事件来轻松做到这一点:

var timeoutCts=new CancellationTokenSource(5000);

await host.RunAsync(timeoutCts.Token);

推荐阅读