.NET Core+Topshelf调用StopApplication()致服务挂起问题求助
问题:Topshelf托管的.NET Core服务自行停止时启动挂起及优雅停止优化
场景与需求
- 基于.NET Core的Windows服务,用Topshelf做托管
- 需要服务在特定触发条件下(测试场景是启动后5秒)自行停止
- 硬性限制:
CreateAndStartHost()方法不能修改,它的Run()扩展本质是Task.Factory.StartNew()的封装
当前核心问题
- 服务启动时出现挂起
- 现有可行方案太繁琐:要在
ApplicationStopping.Register(() => { ... })里调用不等待的_host.StopAsync(),再在ApplicationStopped.Register(() => { ... })中设置标记,最后在TestService.Stop()里等这个标记。想直接在TestService.Stop()里await StopAsync() - 最头疼的点:
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
相关产品推荐
相关产品推荐

