Windows服务循环重试启动报错及停止异常问题咨询
Windows服务数据库连接失败后自动重试的问题与解决方法
问题场景
开发的Windows服务依赖数据库连接,当数据库不可用时服务会抛出异常并停止。希望实现服务循环尝试连接直至成功,但当前代码运行后出现以下问题:
- 首次启动因数据库不可用报错,等待30秒重试时提示
Service cannot be started. An instance of the service is already running. - 手动停止服务时崩溃,抛出
无法访问已释放的对象。对象名称: 'EventLogInternal'异常
用户原代码如下:
public class Program { public static void Main(string[] args) { var appConfigurationManager = new AppConfigurationManager(); bool startupSuccessful = false; do { using (var host = CreateHostBuilder(args, appConfigurationManager).Build()) try { host.Run(); startupSuccessful = true; } catch (Exception ex) { Thread.Sleep(TimeSpan.FromSeconds(30)); } } while (!startupSuccessful); } public static IHostBuilder CreateHostBuilder(string[] args, IAppConfigurationManager appConfigurationManager) { return Host.CreateDefaultBuilder(args) .UseWindowsService(options => { options.ServiceName = $"MyServiceName"; }) .ConfigureServices((context, services) => { // 省略其他服务注册 services.AddHostedService<SomeBackgroundService>(); // 省略其他服务注册 }); } }
问题根源
- 服务状态冲突:Windows服务控制管理器会跟踪服务运行状态,首次
host.Run()抛出异常后,管理器可能已标记服务为启动状态,再次创建并运行Host会触发"服务已运行"的冲突。 - 资源释放异常:
using语句会释放Host实例,其中包含的EventLog日志组件也会被销毁。手动停止服务时,Windows服务生命周期尝试写入日志,就会访问已释放的EventLogInternal对象,导致ObjectDisposedException。
正确实现方案
方案一:后台服务内部处理重试
将数据库连接及业务逻辑的重试逻辑放在SomeBackgroundService内部,保持Host始终运行,避免重启Host带来的状态冲突和资源问题。
示例代码:
public class SomeBackgroundService : BackgroundService { private readonly ILogger<SomeBackgroundService> _logger; private readonly IDbConnectionFactory _dbConnectionFactory; public SomeBackgroundService(ILogger<SomeBackgroundService> logger, IDbConnectionFactory dbConnectionFactory) { _logger = logger; _dbConnectionFactory = dbConnectionFactory; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { try { // 尝试连接数据库 using var connection = await _dbConnectionFactory.CreateConnectionAsync(stoppingToken); await connection.OpenAsync(stoppingToken); _logger.LogInformation("数据库连接成功,执行业务逻辑"); // 执行核心业务逻辑 await RunBusinessLogic(connection, stoppingToken); // 业务逻辑执行完成后,按需设置等待间隔 await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken); } catch (Exception ex) { _logger.LogError(ex, "数据库连接或业务逻辑失败,30秒后重试"); await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken); } } } private async Task RunBusinessLogic(IDbConnection connection, CancellationToken stoppingToken) { // 替换为你的业务逻辑实现 } }
方案二:Host启动前完成连接重试
如果要求Host启动前必须确保数据库可用,可以在Host构建完成后、启动前添加连接重试逻辑,避免重启整个Host。
示例代码:
public class Program { public static async Task Main(string[] args) { var appConfigurationManager = new AppConfigurationManager(); var host = CreateHostBuilder(args, appConfigurationManager).Build(); // 启动前先确保数据库连接成功 using (var scope = host.Services.CreateScope()) { var services = scope.ServiceProvider; var logger = services.GetRequiredService<ILogger<Program>>(); var dbConnectionFactory = services.GetRequiredService<IDbConnectionFactory>(); while (true) { try { using var connection = await dbConnectionFactory.CreateConnectionAsync(CancellationToken.None); await connection.OpenAsync(CancellationToken.None); logger.LogInformation("数据库连接成功,启动服务"); break; } catch (Exception ex) { logger.LogError(ex, "数据库连接失败,30秒后重试"); await Task.Delay(TimeSpan.FromSeconds(30)); } } } await host.RunAsync(); } public static IHostBuilder CreateHostBuilder(string[] args, IAppConfigurationManager appConfigurationManager) { return Host.CreateDefaultBuilder(args) .UseWindowsService(options => { options.ServiceName = "MyServiceName"; }) .ConfigureServices((context, services) => { services.AddSingleton<IDbConnectionFactory, YourDbConnectionFactory>(); services.AddHostedService<SomeBackgroundService>(); // 省略其他服务注册 }); } }
内容的提问来源于stack exchange,提问作者garyore
相关产品推荐
相关产品推荐

