英文:
.NET Core application hanging when using Topshelf with IApplicationHostLifetime.StopApplication()
问题
我有一个运行在Windows服务中的.NET Core应用程序,使用Topshelf来管理。我想要实现的是,服务能够在某个触发条件下(在我的测试应用中,启动后几秒钟)停止自身。问题是,我的应用似乎在启动时出现了问题,而且我对目前找到的唯一解决方案并不满意。
这是我能够想到的最简单的代码示例。您可能会注意到稍微奇怪的CreateAndStartHost()
方法,这个方法基本上是从我们实际的应用程序中复制/粘贴过来的,所以我们不能真正改变它。Run()
扩展方法基本上只是Task.Factory.StartNew()
的包装。
到目前为止唯一有效的方法是在ApplicationStopping.Register(() => { ... })
中调用并且不等待_host.StopAsync()
,然后在ApplicationStopped.Register(() => { ... })
中设置一些标志,然后在TestService.Stop()
中等待它。这种方法的问题在于,我仍然希望能够在TestService.Stop()
中等待StopAsync()
,而将其保存在一个Task
中似乎有点太过巧妙。
// Program.cs - 创建并运行Topshelf主机
HostFactory.New(x =>
{
x.Service<TestService>(sc =>
{
sc.ConstructUsing(() => new TestService());
sc.WhenStarted((s, hostControl) => s.Start(hostControl));
sc.WhenStopped((s, hostControl) => s.Stop(hostControl));
sc.WhenShutdown((s, hostControl) =>
{
Log.Information("On TopShelf Shutdown");
s.Stop(hostControl);
});
});
x.SetDescription("TopshelfWithDotNetDi");
x.SetDisplayName("TopshelfWithDotNetDi");
x.SetServiceName("TopshelfWithDotNetDi");
x.RunAsLocalSystem();
x.StartAutomaticallyDelayed();
x.EnableShutdown();
x.UnhandledExceptionPolicy = UnhandledExceptionPolicyCode.LogErrorOnly;
x.OnException(ex =>
{
Log.Information(ex.Message);
});
}).Run();
// TestServices.cs
public class TestService : ServiceControl
{
private IHost _host;
public bool Start(HostControl hostControl)
{
Log.Information("Starting host...");
_host = CreateAndStartHost().GetAwaiter().GetResult();
var hostApplicationLifetime = _host.Services.GetService<IHostApplicationLifetime>();
hostApplicationLifetime.ApplicationStopping.Register(() => { hostControl.Stop(); });
hostApplicationLifetime.ApplicationStopped.Register(() => { Log.Information("Application stopped..."); });
return true;
}
public bool Stop(HostControl hostControl)
{
Log.Information("Stopping host...");
var stopTask = _host.StopAsync();
while (!stopTask.Wait(1000)) // <-- 问题:这将永远循环
{
hostControl.RequestAdditionalTime(TimeSpan.FromSeconds(1));
}
return true;
}
private static Task<IHost> CreateAndStartHost()
{
// 无法真正更改此部分,抱歉
var taskScheduler = new AsyncContextThread().Factory.Scheduler;
return taskScheduler.Run(async () =>
{
var host = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<MyHostedService>();
}).Build();
await host.StartAsync();
return host;
});
}
}
// MyHostedService 只会在5秒循环后调用 _applicationLifetimeHost.StopApplication()
// 所以我不在示例中包含这部分
请注意,以上代码中的注释是我提供的翻译。
英文:
I have a .NET Core application that is running as a Windows service using Topshelf. What I want to do is for the service to be able to stop itself on some trigger (in my test app, a few seconds after startup). The problem is that my app seems to hang on startup and I'm not quite happy with the only solution I found so far.
This is the simplest code sample I could come up with. You might notice the slightly weird CreateAndStartHost()
, that one is mostly copy/pasted from our actual app, so we can't really change that. The Run()
extension method is basically just a wrapper over Task.Factory.StartNew()
.
The only thing that worked so far is to call and not wait _host.StopAsync()
in ApplicationStopping.Register(() => { ... })
and then set some flag in ApplicationStopped.Register(() => { ... })
which will then be waited for in TestService.Stop()
. The problem with this approach is that I would still like to be able to await StopAsync()
inside TestService.Stop()
and saving it in a Task
feels a bit too hacky.
// Program.cs - Create and run Topshelf host
HostFactory.New(x =>
{
x.Service<TestService>(sc =>
{
sc.ConstructUsing(() => new TestService());
sc.WhenStarted((s, hostControl) => s.Start(hostControl));
sc.WhenStopped((s, hostControl) => s.Stop(hostControl));
sc.WhenShutdown((s, hostControl) =>
{
Log.Information("On TopShelf Shutdown");
s.Stop(hostControl);
});
});
x.SetDescription("TopshelfWithDotNetDi");
x.SetDisplayName("TopshelfWithDotNetDi");
x.SetServiceName("TopshelfWithDotNetDi");
x.RunAsLocalSystem();
x.StartAutomaticallyDelayed();
x.EnableShutdown();
x.UnhandledExceptionPolicy = UnhandledExceptionPolicyCode.LogErrorOnly;
x.OnException(ex =>
{
Log.Information(ex.Message);
});
}).Run();
// TestServices.cs
public class TestService : ServiceControl
{
private IHost _host;
public bool Start(HostControl hostControl)
{
Log.Information("Starting host...");
_host = CreateAndStartHost().GetAwaiter().GetResult();
var hostApplicationLifetime = _host.Services.GetService<IHostApplicationLifetime>();
hostApplicationLifetime.ApplicationStopping.Register(() => { hostControl.Stop(); });
hostApplicationLifetime.ApplicationStopped.Register(() => { Log.Information("Application stopped..."); });
return true;
}
public bool Stop(HostControl hostControl)
{
Log.Information("Stopping host...");
var stopTask = _host.StopAsync();
while (!stopTask.Wait(1000)) // <-- PROBLEM: This will loop forever
{
hostControl.RequestAdditionalTime(TimeSpan.FromSeconds(1));
}
return true;
}
private static Task<IHost> CreateAndStartHost()
{
// Can't really change this bit, sorry
var taskScheduler = new AsyncContextThread().Factory.Scheduler;
return taskScheduler.Run(async () =>
{
var host = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<MyHostedService>();
}).Build();
await host.StartAsync();
return host;
});
}
}
// MyHostedService will just call _applicationLifetimeHost.StopApplication()
// after a 5s loop, so I'm not including this bit in the sample
答案1
得分: 1
这可能不是你寻找的答案,但我不会使用Topshelf。它似乎是一个被放弃的项目(最近的提交是三年前)。
你不需要Topshelf来能够在Windows服务中运行.NET,因为这在本地得到支持:
英文:
This might not be the answer you're looking for, but I wouldn't use Topshelf. It seems like an abandoned project (latest commit three years ago).
You don't need Topshelf to be able to run .NET in a Windows service as this is supported natively:
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论