diff --git a/NDB.Hosting.WindowsService/Class1.cs b/NDB.Hosting.WindowsService/Class1.cs deleted file mode 100644 index cf38fd2..0000000 --- a/NDB.Hosting.WindowsService/Class1.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; - -namespace NDB.Hosting.WindowsService -{ - public class Class1 - { - } -} diff --git a/NDB.Hosting.WindowsService/ServiceBaseLifetime.cs b/NDB.Hosting.WindowsService/ServiceBaseLifetime.cs new file mode 100644 index 0000000..c483e97 --- /dev/null +++ b/NDB.Hosting.WindowsService/ServiceBaseLifetime.cs @@ -0,0 +1,63 @@ +using Microsoft.Extensions.Hosting; +using System; +using System.ServiceProcess; +using System.Threading; +using System.Threading.Tasks; + +namespace NDB.Hosting.WindowsService +{ + public class ServiceBaseLifetime : ServiceBase, IHostLifetime + { + private readonly TaskCompletionSource _delayStart = new TaskCompletionSource(); + + public ServiceBaseLifetime(IApplicationLifetime applicationLifetime) + { + ApplicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime)); + } + + private IApplicationLifetime ApplicationLifetime { get; } + + public Task WaitForStartAsync(CancellationToken cancellationToken) + { + cancellationToken.Register(() => _delayStart.TrySetCanceled()); + ApplicationLifetime.ApplicationStopping.Register(Stop); + + new Thread(Run).Start(); // Otherwise this would block and prevent IHost.StartAsync from finishing. + return _delayStart.Task; + } + + private void Run() + { + try + { + Run(this); // This blocks until the service is stopped. + _delayStart.TrySetException(new InvalidOperationException("Stopped without starting")); + } + catch (Exception ex) + { + _delayStart.TrySetException(ex); + } + } + + public Task StopAsync(CancellationToken cancellationToken) + { + Stop(); + return Task.CompletedTask; + } + + // Called by base.Run when the service is ready to start. + protected override void OnStart(string[] args) + { + _delayStart.TrySetResult(null); + base.OnStart(args); + } + + // Called by base.Stop. This may be called multiple times by service Stop, ApplicationStopping, and StopAsync. + // That's OK because StopApplication uses a CancellationTokenSource and prevents any recursion. + protected override void OnStop() + { + ApplicationLifetime.StopApplication(); + base.OnStop(); + } + } +} diff --git a/NDB.Hosting.WindowsService/ServiceBaseLifetimeHostExtensions.cs b/NDB.Hosting.WindowsService/ServiceBaseLifetimeHostExtensions.cs new file mode 100644 index 0000000..cc46608 --- /dev/null +++ b/NDB.Hosting.WindowsService/ServiceBaseLifetimeHostExtensions.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System.Threading; +using System.Threading.Tasks; + +namespace NDB.Hosting.WindowsService +{ + public static class ServiceBaseLifetimeHostExtensions + { + public static IHostBuilder UseServiceBaseLifetime(this IHostBuilder hostBuilder) + { + return hostBuilder.ConfigureServices((hostContext, services) => services.AddSingleton()); + } + + public static Task RunAsServiceAsync(this IHostBuilder hostBuilder, CancellationToken cancellationToken = default) + { + return hostBuilder.UseServiceBaseLifetime().Build().RunAsync(cancellationToken); + } + } +}