如何在BackgroundService获取ASP.NET Core启动地址

前言

上次,我们介绍了《如何获取 ASP.NET Core 启动地址》。

但是,如果要在 BackgroundService 中获取启动地址可不那么容易,因为 BackgroundService 在 app 启动前就开始执行了:.

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHostedService<DemoBackgroundService>();

var app = builder.Build();

app.MapGet("/", () => "Hello My IO!");

await app.StartAsync();

Console.WriteLine(app.Urls.First());

await app.WaitForShutdownAsync();

internal class DemoBackgroundService : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        Console.WriteLine("BackgroundService ExecuteAsync");
        await Task.CompletedTask;
    }
}

如何在BackgroundService获取ASP.NET Core启动地址

那要怎么实现呢?

IHostApplicationLifetime

我们可以利用ASP.NET Core应用程序生存期事件,等待启动完成再获取启动地址:

internal class DemoBackgroundService : BackgroundService
{
    private readonly IServiceProvider _services;
    private string _url;

    public DemoBackgroundService(IServiceProvider services, IHostApplicationLifetime lifetime)
    {
        _services = services;
        lifetime.ApplicationStarted.Register(OnAppStarted);
    }
    public void OnAppStarted()
    {
        var server = _services.GetService<IServer>();
        _url = server.Features.Get<IServerAddressesFeature>().Addresses.First();
    }
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (string.IsNullOrWhiteSpace(_url))
        {
            await Task.Delay(1000); 
        }
        
        Console.WriteLine("BackgroundService ExecuteAsync");
        Console.WriteLine(_url);

        await Task.CompletedTask;
    }
}

现在可以正常获取 ASP.NET Core 启动地址了:

如何在BackgroundService获取ASP.NET Core启动地址

SemaphoreSlim

轮询虽然可以解决问题,但感觉不太优雅。

我们可以创建一个信号灯 SemaphoreSlim,在ExecuteAsync方法中等待信号灯,而在启动后释放信号灯:

static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(0, 1);

public void OnAppStarted()
{
    ...
    semaphoreSlim.Release();
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    await semaphoreSlim.WaitAsync();
    ...
}

可以看到,这样还带来了另外一个好处,比上面的解决方式更快地进入到了 BackgroundService:

如何在BackgroundService获取ASP.NET Core启动地址

结论

今天,我们利用了ASP.NET Core应用程序生存期事件,实现了在 BackgroundService 获取 ASP.NET Core 启动地址。