c# - 在 .net core 3.0 BackgroundService 应用程序中,为什么我的配置对象在作为服务运行时为空,而不是作为控制台应用程序运行?
问题描述
我有一个 .net Core 3.0 BackgroundService 应用程序,在控制台模式下运行时工作正常,但是一旦我部署为服务,应该从 appsettings.json 加载的配置对象为空。是什么赋予了?
程序.cs
public class Program
{
public static async System.Threading.Tasks.Task Main(string[] args)
{
var hostbuilder = new HostBuilder()
.UseWindowsService()
.ConfigureAppConfiguration((hostingContext, config) =>
{
config
.SetBasePath(Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location))
.AddJsonFile("appsettings.json");
})
.ConfigureLogging(
options => options.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Information))
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Importer>().Configure<EventLogSettings>(config =>
{
config.LogName = "Application";
config.SourceName = "Importer";
});
});
#if (DEBUG)
await hostbuilder.RunConsoleAsync();
#else
await hostbuilder.RunAsServiceAsync();
#endif
}
}
IhostBuilder运行服务的扩展方法
public static class ServiceBaseLifetimeHostExtensions
{
public static IHostBuilder UseServiceBaseLifetime(this IHostBuilder hostBuilder)
{
return hostBuilder.ConfigureServices((hostContext, services) => services.AddSingleton<IHostLifetime, ServiceBaseLifetime>());
}
public static Task RunAsServiceAsync(this IHostBuilder hostBuilder, CancellationToken cancellationToken = default)
{
return hostBuilder.UseServiceBaseLifetime().Build().RunAsync(cancellationToken);
}
}
处理服务生命周期的 ServiceBaseLifetime 类
public class ServiceBaseLifetime : ServiceBase, IHostLifetime
{
private readonly TaskCompletionSource<object> _delayStart = new TaskCompletionSource<object>();
public ServiceBaseLifetime(IHostApplicationLifetime applicationLifetime)
{
ApplicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
}
private IHostApplicationLifetime 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();
}
}
服务的实际实现与构造函数无关,构造函数通过 DI 获取记录器和配置。
private readonly ILogger<Importer> _logger;
private readonly IConfiguration _configuration;
public Importer(IConfiguration configuration, ILogger<Importer> logger)
{
_logger = logger;
_configuration = configuration;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation($"Why is {_configuration["Key1"]} empty?");
}
应用设置.json
{
"Key1":"some value"
}
当我运行调试时,控制台应用程序启动并运行并记录并从 appsettings 加载配置。当我部署为服务时,配置对象为空。
注意:正在读取 appsettings 文件,我可以通过更改它的名称来判断这一点,它会抛出找不到文件的异常。appsettings 文件也不为空。
解决方案
我的问题似乎是某种异步竞争条件问题(我猜,不是肯定的)。通过ExecuteAsync
配置的第一次滴答未加载,但第二次通过它。如果它遇到那个异常,我的服务就会死掉,所以我再也没有让它第二次打勾。
推荐阅读
- python - pmdarima 将对象分配给 auto_arima 输出
- qt - 在 qt 地图中使用 tileserver-gl 主机
- amazon-web-services - 我可以使用 S3 托管图像,并在我的 EC2 网站上使用它们吗?
- swift - Swift 中的 UiSearchBar 到 UiCollectionView
- ruby-on-rails - Rails 对空路径的约定是什么?
- javascript - 如何避免使用“回调地狱”并转向 Promises?
- mysql - MySQL 特定时间的日期时间值不正确
- reactjs - Gatsby 代理到具有相同前缀的多个后端
- python - Applescript 无法获取 Python 调用的返回
- php - 上传时,Laravel 文件名未正确保存在数据库中