NET Core 3 服务 - 服务管理器不会等待服务启动



我有作为服务运行的应用程序。由于连接到数据库等原因,应用程序启动了几秒钟。我有另一个服务应用程序,取决于第一个。

因此,我需要区分"启动阶段"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();
}

_delayStopWaitForStartAsync方法期间挂接,这起到两个作用:它为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

最新更新