Reputation: 1328
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
Upvotes: 0
Views: 393
Reputation: 4418
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:
Upvotes: 1