我有作为服务运行的应用程序。由于连接到数据库等原因,应用程序启动了几秒钟。我有另一个服务应用程序,取决于第一个。
因此,我需要区分"启动阶段"one_answers"运行阶段",以便知道何时启动另一项服务。
为了简化问题,我声明了如下示例所示的服务:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
})
.UseWindowsService();
}
public class Worker : IHostedService
{
public async Task StartAsync(CancellationToken cancellationToken)
{
// simulate loading... non of this cause wait
Thread.Sleep(5000);
await Task.Delay(5000);
}
public async Task StopAsync(CancellationToken cancellationToken)
{
await Task.Delay(1);
}
}
我通过Windows服务管理器启动程序来重现这个问题,它在一秒钟内启动,绝对不是10秒钟(在这种情况下(。
注1:我玩过BackgroundService类的inhering,但结果基本相同。
注意#2:使用扩展方法.UseSystemd()
时,在带有systemd的linux上的行为是相同的。
有什么方法可以将服务的启动和执行分开吗?
首先,我建议您向请求此行为的MS提出一个问题。有许多意外的行为。NET核心Win32服务。
UseWindowsService
安装一个WindowsServiceLifetime
,用于处理之间的交互。NET核心主机和Win32 SCM。该类型的源代码在这里。
特别是,OnStart
的实现看起来是这样的:
protected override void OnStart(string[] args)
{
_delayStart.TrySetResult(null);
base.OnStart(args);
}
因此,当SCM发送"启动"请求时,它将允许。NET主机启动以继续(_delayStart.TrySetResult
(,但在从OnStart
返回之前不会等待启动完成(这向SCM发出服务已启动的信号(。
您需要阻止OnStart
,直到启动完成。有趣的是,WindowsServiceLifetime
本身有一个可以用于此目的的模式;当SCM发送"停止"请求时,WindowsServiceLifetime
启动。NET主机关闭过程,然后等待它完成,再从OnStop
:返回
protected override void OnStop()
{
ApplicationLifetime.StopApplication();
// Wait for the host to shutdown before marking service as stopped.
_delayStop.Wait(_hostOptions.ShutdownTimeout);
base.OnStop();
}
_delayStop
在WaitForStartAsync
方法期间挂接,这起到两个作用:它为IHostLifetime
提供了一种暂停主机启动的方式(在这种情况下,直到调用OnStart
为止(,并为IHostLifetime
提供初始化级别的挂接,以挂接到主机事件中。
该方法的相关部分包括:
public Task WaitForStartAsync(CancellationToken cancellationToken)
{
...
ApplicationLifetime.ApplicationStopped.Register(() =>
{
_delayStop.Set();
});
...
return _delayStart.Task;
}
换句话说;当主机关闭完成时通过_delayStop
通知我并延迟主机启动直到我设置了_delayStart
〃;。
您需要做的是使用相同的模式来延迟OnStart
方法。因此,您需要一个带有_applicationStarted
信号的CustomWindowsServiceLifetime
,在应用程序完成启动时设置该信号,并在重写的OnStart
中阻止该信号。然而,由于我们需要覆盖OnStart
,我们还需要提供我们自己的_delayStart
:
public sealed class CustomWindowsServiceLifetime : WindowsServiceLifetime, IHostLifetime
{
private ManualResetEventSlim _applicationStarted = new ManualResetEventSlim();
private TaskCompletionSource<object> _delayStart = new TaskCompletionSource<object>();
public CustomWindowsServiceLifetime(IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory, IOptions<HostOptions> optionsAccessor)
: base(environment, applicationLifetime, loggerFactory, optionsAccessor)
{
ApplicationLifetime = applicationLifetime;
_applicationStarted = new TaskCompletionSource<object>();
}
private IHostApplicationLifetime ApplicationLifetime { get; }
public new Task WaitForStartAsync(CancellationToken cancellationToken)
{
ApplicationLifetime.ApplicationStarted.Register(() =>
{
_applicationStarted.Set();
});
return Either(base.WaitForStartAsync(cancellationToken), _delayStart.Task);
async Task Either(Task a, Task b) => await await Task.WhenAny(a, b);
}
protected override void OnStart(string[] args)
{
// Allow host startup to continue.
_delayStart.TrySetResult(null);
// Wait for host startup to complete before returning to SCM.
_applicationStarted.Wait();
base.OnStart(args);
}
}
这完全没有经过测试(甚至没有经过编译(,但它可能会起作用。
我只是想完成Stephen Cleary的精彩回答-非常感谢!!-小细节。
在上编译的代码。NET Core 3.1(99%与提供的示例相同(。
public sealed class CustomWindowsServiceLifetime : WindowsServiceLifetime, IHostLifetime
{
private ManualResetEventSlim _applicationStarted;
private TaskCompletionSource<object> _delayStart = new TaskCompletionSource<object>();
private IHostApplicationLifetime ApplicationLifetime { get; }
public CustomWindowsServiceLifetime(
IHostEnvironment environment,
IHostApplicationLifetime applicationLifetime,
ILoggerFactory loggerFactory,
IOptions<HostOptions> optionsAccessor
) : base(environment, applicationLifetime, loggerFactory, optionsAccessor)
{
ApplicationLifetime = applicationLifetime;
_applicationStarted = new ManualResetEventSlim();
}
public new Task WaitForStartAsync(CancellationToken cancellationToken)
{
ApplicationLifetime.ApplicationStarted.Register(() =>
{
_applicationStarted.Set();
});
// Must wait
return Either(base.WaitForStartAsync(cancellationToken), _delayStart.Task);
async Task Either(Task a, Task b) => await await Task.WhenAny(a, b);
}
protected override void OnStart(string[] args)
{
// Allow host startup to _proceed_
_delayStart.TrySetResult(null);
// Wait for host startup to _complete_ before returning to SCM.
// -> only then will base.OnStart complete _delayStart.Task
_applicationStarted.Wait();
base.OnStart(args);
}
}
通过将以下内容添加到主机创建中来配置此生存期:
hostBuilder.UseWindowsService();
hostBuilder.ConfigureServices((hostContext, services) => services.AddSingleton<IHostLifetime, CustomWindowsServiceLifetime>());
如果你来这里是因为(像我一样(你在Windows服务和。NET Core,我也可以借此机会推荐以下资源:
https://andrewlock.net/introducing-ihostlifetime-and-untangling-the-generic-host-startup-interactions/
https://cdmana.com/2021/06/20210625104851403p.html
https://blog.stephencleary.com/2020/06/servicebase-gotcha-recovery-actions.html