.NET 8 Host 的一些更新

Intro

.NET 8 里针对 Host 做了一些更新,除了前面提到的 IHostedLifecycleService 之外,还支持的 HostedService 的并发地启动和停止,还抽象了在 .NET 7 开始支持的 HostApplicationBuilder 引入了 IHostApplicationBuilder API,并且引入了一个 Host.CreateEmptyApplicationBuilder 来简化配置一个空的 Host.

Sample

var hostBuilder = Host.CreateEmptyApplicationBuilder(null);
hostBuilder.ConfigureHostOptions(x =>
{
    x.ServicesStartConcurrently = true;
    x.ServicesStopConcurrently = true;
    x.StartupTimeout = TimeSpan.FromMilliseconds(100);
});
hostBuilder.Services.AddHostedService<DelayService>();
hostBuilder.Services.AddHostedService<ReportTimeService>();
var host = hostBuilder.Build();
await host.RunAsync(cancellationToken);

Host.CreateEmptyApplicationBuilder 是 .NET 8 里新增的一个 API 会创建一个空的 Host,不会注册 json,环境变量等配置,不会注册默认的服务如日志配置等

这里的 ConfigureHostOptions 是自己做了一个封装,为了类似于 IHostBuilder 一样简化 HostOptions 的配置,实现如下:

public static IHostApplicationBuilder ConfigureHostOptions(this IHostApplicationBuilder hostBuilder, Action<HostOptions> configureOptions)
{
    hostBuilder.Services.Configure(configureOptions);
    return hostBuilder;
}

HostOptions 里的 ServicesStartConcurrently/ServicesStopConcurrently 以及 StartupTimeout 是 .NET 8 里新增的配置,两个 Concurrently 是一起新增的,默认值是 falseStartupTimeout 是和前面介绍的 IHostedLifecycleService 一起新增的,上次没有提起觉得放在这里更加合适,默认没有限制

DelayService 实现如下:

file class DelayService : IHostedService
{
    public async Task StartAsync(CancellationToken cancellationToken)
    {
        await Task.Delay(TimeSpan.FromMilliseconds(60), cancellationToken);
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        return Task.CompletedTask;
    }
}

file sealed class Delay2Service : DelayService
{
}

为了更好查看效果,增加一个报告时间的后台服务,每秒钟打印一次时间:

file sealed class ReportTimeService : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        using var timer = new PeriodicTimer(TimeSpan.FromSeconds(1));
        while (await timer.WaitForNextTickAsync(stoppingToken))
        {
            Console.WriteLine(DateTimeOffset.Now);
        }
    }
}

前面示例我们设置的启动超时时间是 100ms,两个 delay service 分别是 60ms,如果串行启动的话则会超时,并行启动的话应该不会超时,我们来测试一下,首先我们把两个 concurrently 的配置去掉使用默认值 false 启动

.NET 8 Host 的一些更新

 

再把他们加回来,并行启动试一下

.NET 8 Host 的一些更新

 

可以看到设置为 true 之后就可以正常启动起来了

More

Host.CreateEmptyApplicationBuilder 这个方法是为了 AOT,之前的方法会加载一系列的配置导致 AOT 变得困难,尤其是 console logging 相关的组件影响,并且这一方法也是 asp.net 里新加的 WebApplication.CreateSlimBuilder()/WebApplication.CreateEmptyBuilder() 的基础,这两个方法也是为了 AOT 的需求,这两个方法和原来的 WebApplication.CreateBuilder() 我们后面有机会再单独介绍一下

ServicesStartConcurrently/ServicesStopConcurrently 默认是 false,如果项目里有比较多的 HostedService 影响服务启动的时候可以考虑这一配置,但是如果想要设置为 true 需要考虑一下有没有启动顺序的要求,如果有启动顺序的要求则不适合使用这一配置,前面介绍的 IHostedLifecycleService  也会受这两个参数的影响,如果设置为 true 的话他们也是并发执行

针对 Host.CreateEmptyApplicationBuilder 这一方法,方法签名如下:

public static HostApplicationBuilder CreateEmptyApplicationBuilder(HostApplicationBuilderSettings? settings)

觉得既然 settings 参数允许为  null, 应该有个默认值 null,这样也就不需要像前面示例一样还要显式的传递一个 null,提了一个 issue 希望能够增加一个没有参数的重载或者设置一个默认值 https://github.com/dotnet/runtime/issues/90477

另外一个就是前面的 ConfigureHostOptions 方法,既然针对 IHostBuilder 有 ConfigureHostOptions 的扩展,针对 IHostApplicationBuilder 感觉也应该有这一扩展来简化 host 的配置,也提了一个 issue https://github.com/dotnet/runtime/issues/90478

目前两个 issue 的 milestone 都是 9.0,8.0 大概是不会有了