You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Core+Topshelf调用StopApplication()致服务挂起问题求助

问题:Topshelf托管的.NET Core服务自行停止时启动挂起及优雅停止优化

场景与需求

  • 基于.NET Core的Windows服务,用Topshelf做托管
  • 需要服务在特定触发条件下(测试场景是启动后5秒)自行停止
  • 硬性限制:CreateAndStartHost()方法不能修改,它的Run()扩展本质是Task.Factory.StartNew()的封装

当前核心问题

  1. 服务启动时出现挂起
  2. 现有可行方案太繁琐:要在ApplicationStopping.Register(() => { ... })里调用不等待的_host.StopAsync(),再在ApplicationStopped.Register(() => { ... })中设置标记,最后在TestService.Stop()里等这个标记。想直接在TestService.Stop()里await StopAsync()
  3. 最头疼的点:TestService.Stop()里的stopTask.Wait(1000)会陷入永久循环(触发逻辑是MyHostedService运行5秒后调用_applicationLifetimeHost.StopApplication())

优化解决方案

思路

利用Topshelf对异步停止的支持,结合.NET Host的生命周期事件,打破同步等待导致的死锁,同时实现直接await停止逻辑。

代码实现

先调整TestService的停止逻辑,改用异步处理:

public class TestService
{
    private readonly IHost _host;
    private readonly CancellationTokenSource _stopCts = new CancellationTokenSource();
    private Task? _hostStopTask;

    public TestService(IHost host)
    {
        _host = host;
        // 监听ApplicationStopping事件,触发Host停止任务
        _host.Services.GetRequiredService<IHostApplicationLifetime>().ApplicationStopping.Register(() =>
        {
            _hostStopTask = _host.StopAsync(_stopCts.Token);
        });
    }

    public void Start()
    {
        // 调用不可修改的原有启动逻辑
        CreateAndStartHost();
    }

    public async Task Stop()
    {
        if (_hostStopTask == null)
        {
            // 若未触发自动停止,主动发起停止流程
            _hostStopTask = _host.StopAsync(_stopCts.Token);
        }
        try
        {
            await _hostStopTask;
        }
        finally
        {
            _stopCts.Dispose();
        }
    }

    // 不可修改的方法,保留原有逻辑
    private void CreateAndStartHost()
    {
        // 原有代码,内部调用Run()(封装Task.Factory.StartNew())
    }
}

然后在Topshelf的配置里使用异步停止扩展(Topshelf 4.x及以上版本支持异步生命周期方法):

HostFactory.Run(x =>
{
    x.Service<TestService>(s =>
    {
        s.ConstructUsing(name => new TestService(CreateHost()));
        s.WhenStarted(service => service.Start());
        // 改用异步停止方法
        s.WhenStopped(async service => await service.Stop());
    });
    // 其他Topshelf配置项(如服务名称、描述等)
});

方案说明

  • 用Topshelf的异步WhenStopped替代同步停止,避免主线程阻塞导致的死锁/挂起
  • 通过IHostApplicationLifetime.ApplicationStopping事件触发停止任务,让自动停止和主动停止复用同一逻辑,不用额外维护标记
  • 用CancellationTokenSource管理停止信号,保证资源正确释放
  • 完全实现了在Stop()方法里直接await StopAsync()的需求,去掉了繁琐的标记等待逻辑

原有Wait(1000)永久循环的原因

StopAsync()内部会等待所有托管服务停止,而Topshelf的同步Stop()方法运行在服务主线程中。此时MyHostedService可能在等待主线程完成Stop()逻辑,就形成了死锁:StopAsync()等托管服务结束,托管服务等主线程释放,最终导致永久挂起。改用异步WhenStopped后,Topshelf会在单独线程处理停止逻辑,直接打破这个死锁链。

内容的提问来源于stack exchange,提问作者Cristi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 22:30:22