You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

调用IHost.StopAsync时IHostedService.StopAsync被调用两次的原因及解决

.NET Core 2.2通用主机中IHostedService.StopAsync重复调用问题解析

先看你给出的代码和运行结果:

示例代码

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被调用两次的问题,下面逐一解答:

1. 重复调用的原因

这是.NET Core 2.2通用主机的设计缺陷

  • 调用host.RunAsync()时,方法不仅会启动主机,还会内部注册终止信号(如Ctrl+C)的处理逻辑,同时等待主机停止令牌触发。
  • 手动调用host.StopAsync()会主动触发主机停止流程,调用所有IHostedServiceStopAsync
  • RunAsync内部检测到主机状态变为停止后,会再次执行一次停止清理逻辑,导致StopAsync被第二次调用。

简单来说就是手动停止和RunAsync的内部停止流程没有做好协调,导致了重复触发。

2. 是否符合预期?有没有配置遗漏?

在.NET Core 2.2版本中,这种行为符合当时框架的实现逻辑,但绝对不是开发者想要的预期结果。这不算配置遗漏,纯粹是早期通用主机的设计问题——微软在后续版本中已经修复了这个bug。

3. 如何让StopAsync仅调用一次?

有几种可行的解决方案:

方案一:用取消令牌替代手动调用StopAsync

通过CancellationTokenSource控制主机停止,让RunAsync统一处理整个停止流程:

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

    var runTask = host.RunAsync(cts.Token);
    await Task.Delay(5000);
    cts.Cancel(); // 触发取消信号,由RunAsync内部处理停止
    await runTask;
}

这种方式下,停止逻辑由RunAsync主导,只会调用一次StopAsync

方案二:升级到.NET Core 3.0及以上版本

微软在.NET Core 3.0时彻底重构了通用主机实现,修复了这个重复调用问题。如果项目允许升级框架版本,这是最一劳永逸的办法——升级后即使手动调用host.StopAsync(),也只会触发一次停止逻辑。

方案三:手动控制主机生命周期,不用RunAsync

绕开RunAsync的内部逻辑,手动启动和停止主机:

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

    await host.StartAsync(); // 手动启动主机
    Console.WriteLine("Application started. Press Ctrl+C to shut down.");
    await Task.Delay(5000);
    await host.StopAsync(); // 手动停止,仅触发一次StopAsync
    await host.WaitForShutdownAsync(); // 可选,等待主机完全关闭
}

这种方式完全由你掌控主机的启动和停止,不会出现重复调用问题。

内容的提问来源于stack exchange,提问作者Jesús López

火山引擎 最新活动