首页 > 解决方案 > 在 .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 文件也不为空。

标签: c#dependency-injection.net-corewindows-services

解决方案


我的问题似乎是某种异步竞争条件问题(我猜,不是肯定的)。通过ExecuteAsync配置的第一次滴答未加载,但第二次通过它。如果它遇到那个异常,我的服务就会死掉,所以我再也没有让它第二次打勾。


推荐阅读