.NET Core 3.1中BackgroundService如何实现RequestAdditionalTime等效功能?
在.NET Core 3.1 Windows服务中请求额外启动时间的解决方案
这个问题我之前帮朋友处理过,确实在.NET Core的跨平台服务模型里,没有直接对应.NET Framework中ServiceBase.RequestAdditionalTime的现成API,但我们有两种可靠的方式来实现相同的效果,让服务控制管理器(SCM)给我们更多时间完成数据库连接这类启动前的准备工作:
方法1:通过P/Invoke调用Windows API手动更新服务状态
.NET Core的Windows服务本质上还是基于Windows原生服务机制运行的,所以我们可以直接调用Windows的SetServiceStatus API,主动向SCM报告服务处于启动中状态,并告知需要延长超时时间。
步骤1:定义Windows API相关的结构体和导入声明
先创建一个辅助类,封装所需的Win32 API调用:
using System.Runtime.InteropServices; public static class WindowsServiceHelper { // 对应Windows的SERVICE_STATUS结构体 [StructLayout(LayoutKind.Sequential)] private struct SERVICE_STATUS { public uint dwServiceType; public uint dwCurrentState; public uint dwControlsAccepted; public uint dwWin32ExitCode; public uint dwServiceSpecificExitCode; public uint dwCheckPoint; public uint dwWaitHint; } // 导入SetServiceStatus API [DllImport("advapi32.dll", SetLastError = true)] private static extern bool SetServiceStatus(IntPtr serviceHandle, ref SERVICE_STATUS serviceStatus); // 服务状态常量 private const uint SERVICE_START_PENDING = 0x00000002; private const uint SERVICE_ACCEPT_STOP = 0x00000001; private const uint SERVICE_WIN32_OWN_PROCESS = 0x00000010; /// <summary> /// 向SCM请求额外的启动时间 /// </summary> /// <param name="serviceHandle">服务句柄</param> /// <param name="milliseconds">需要的额外时间(毫秒)</param> public static void RequestAdditionalStartupTime(IntPtr serviceHandle, int milliseconds) { var status = new SERVICE_STATUS { dwServiceType = SERVICE_WIN32_OWN_PROCESS, dwCurrentState = SERVICE_START_PENDING, // 告诉SCM服务正在启动 dwControlsAccepted = SERVICE_ACCEPT_STOP, // 允许接收停止命令 dwWin32ExitCode = 0, dwServiceSpecificExitCode = 0, dwCheckPoint = 1, // 可以递增这个值来表示启动进度,SCM会认为服务在推进 dwWaitHint = (uint)milliseconds // 告诉SCM我们还需要这么多时间完成启动 }; SetServiceStatus(serviceHandle, ref status); } }
步骤2:在BackgroundService中使用该方法
在你的Worker类中,通过依赖注入获取WindowsServiceLifetime实例,从而拿到服务句柄,然后在启动逻辑中调用辅助方法:
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting.WindowsServices; using System; using System.Threading; using System.Threading.Tasks; public class Worker : BackgroundService { private readonly IHostApplicationLifetime _appLifetime; public Worker(IHostApplicationLifetime appLifetime) { _appLifetime = appLifetime; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { // 只有当服务以Windows服务模式运行时,才调用这个方法 if (_appLifetime is WindowsServiceLifetime windowsServiceLifetime) { // 先请求120秒的启动时间 WindowsServiceHelper.RequestAdditionalStartupTime(windowsServiceLifetime.ServiceHandle, 120 * 1000); } // 重试数据库连接的逻辑 bool isDbConnected = false; int retryCount = 0; while (!isDbConnected && !stoppingToken.IsCancellationRequested) { try { CheckDatabaseCompatibility(); isDbConnected = true; Console.WriteLine("数据库连接成功,开始运行服务逻辑"); } catch (Exception ex) { retryCount++; // 记录日志,这里可以替换成你的日志组件 Console.WriteLine($"第{retryCount}次连接数据库失败:{ex.Message},5秒后重试"); await Task.Delay(5000, stoppingToken); // 每次重试时,可以再次更新超时时间,告诉SCM我们还在推进 if (_appLifetime is WindowsServiceLifetime windowsServiceLifetime) { WindowsServiceHelper.RequestAdditionalStartupTime(windowsServiceLifetime.ServiceHandle, 60 * 1000); } } } // 如果最终还是无法连接数据库,停止服务 if (!isDbConnected) { Console.WriteLine("多次重试后仍无法连接数据库,停止服务"); _appLifetime.StopApplication(); return; } // 正常运行服务的业务逻辑 while (!stoppingToken.IsCancellationRequested) { // 这里写你的服务业务代码 await Task.Delay(1000, stoppingToken); } } private void CheckDatabaseCompatibility() { // 你的数据库版本检查、连接验证逻辑 // 比如:using(var conn = new SqlConnection("...")) { conn.Open(); /* 检查版本 */ } } }
方法2:修改Windows系统的服务启动超时配置
如果你不想修改代码,也可以直接通过系统配置来延长所有Windows服务的启动超时时间:
注册表方式:
- 打开注册表编辑器(
regedit),导航到HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control - 如果不存在
ServicesPipeTimeout项,右键新建DWORD(32位)值,命名为ServicesPipeTimeout - 设置其值为你需要的毫秒数(比如120000代表120秒),基数选择十进制
- 重启电脑后生效
- 打开注册表编辑器(
命令行方式(管理员权限):
# 设置超时时间为120秒(120000毫秒) reg add HKLM\SYSTEM\CurrentControlSet\Control /v ServicesPipeTimeout /t REG_DWORD /d 120000 /f
注意:这个配置是全局生效的,会影响所有Windows服务的启动超时时间,如果你只想针对当前服务调整,方法1是更合适的选择。
内容的提问来源于stack exchange,提问作者Simon Gymer
相关产品推荐
相关产品推荐

