【问题标题】:Why is IHostedService.StopAsync called twice when calling IHost.StopAsync?为什么调用 IHost.StopAsync 时会调用两次 IHostedService.StopAsync?
【发布时间】:2019-08-09 16:50:36
【问题描述】:

鉴于以下使用通用主机的 .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 的主机?

【问题讨论】:

  • StartAsync 和 StopAsync 都由框架本身调用。您不必手动调用它们
  • @Alberto。如果我需要通过键入 ctrl + c 或发送 SIGTERM 以外的其他方式停止主机怎么办?

标签: c# .net-core


【解决方案1】:

因为它被调用了两次 - 一次在延迟之后,另一次在服务实际停止时。使用RunAsync 时不应该调用它。

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

var timeoutCts=new CancellationTokenSource(5000);

await host.RunAsync(timeoutCts.Token);

说明

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

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

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

【讨论】:

  • 我很确定IHostApplicationLifetimeIApplicationLifetime 的netcore 3.0 重命名版本,由于aspnetcore 托管和通用主机之间的命名冲突而引入。方法/属性是相同的,只是为了防止有人尝试使用 2.2 中的接口
  • @onefoots 不会,RunRunAsync 实际启动应用程序和所有服务。例如,在 ASP.NET Core 应用程序中,在调用 Run 之前,什么都不会开始运行。这不是方法的问题 - 你对 Run 的电话有什么看法? host.Start.Async()。您如何在每一个情况下向应用程序发出停止信号?你打电话给StopApplication。事实上,我已经链接到显示与链接答案中相同的调用的源代码
  • @onefootswill 实际上,根本不需要在控制台应用程序中调用RunStartApplication。只需使用主机检索服务和配置,完成后退出您的主要功能。
  • @onefootswill (if that code comes after the invocation of Run). 那是因为Run 是运行应用程序的。它在应用程序终止时退出。在终止期间调用Run 之后的任何代码
  • @onefoots 当您确实需要一些东西来托管和运行您的应用程序时,您是否需要Run(Async)。 WinForms 应用程序不需要其他主机,它已经拥有自己的主机。所有其他服务,例如 DI、配置、日志记录,都可以独立使用。不过,通用主机是一个方便的地方,可以访问所有这些。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-13
相关资源
最近更新 更多