IHostedLifecycleService是如何管理后台任务的
在本文中,我们将介绍 IHostedLifecycleService
接口,并通过几个用例来了解它是如何扩展 IHostedService
接口并实现对托管服务生命周期更精细控制的。
一、什么是 IHostedService 接口
IHostedService
接口是 .NET 中的一个强大功能,我们可以通过它管理应用程序中的后台任务,
它包含两个方法 StartAsync
和 StopAsync
,其中 StartAsync
方法用于实现启动后台任务的逻辑,当服务器启动并且 IHostApplicationLifetime.ApplicationStarted
被触发时会调用这个方法。 当应用程序停止时会调用 StopAsync
方法,我们使用它来停止后台任务并处理未托管的资源。
二、IHostedLifecycleService 接口
IHostedLifecycleService
接口继承自 IHostedService
接口,它对托管服务的启动和停止进行了更精细控制,引入了在服务启动或停止前后执行的新方法:StartingAsync
、StartedAsync
、StoppingAsync
、StoppedAsync
。下面我们来简单了解一下这四个方法的触发时机,首先,StartingAsync
方法会在 StartAsync
方法之前触发,而StartedAsync
方法是在 StartAsync
方法之后触发。同样,在 StoppingAsync
方法之前会触发 StoppedAsync
方法,在 StopAsync
方法之后会触发 StoppedAsync
方法。
下面,我们通过一个简单的例子来看一下在实际开发中的应用。
首先,我们定义了两个类 HostedService1
和 HostedService2
,在这两个类中都实现了 IHostedLifecycleService
接口:
public class HostedService1 (ILogger<HostedService1> logger) : IHostedLifecycleService
{
public Task StartAsync(CancellationToken cancellationToken)
{
logger.LogInformation($"Service {nameof(HostedService1 )} start.");
return Task.CompletedTask;
}
public Task StartedAsync(CancellationToken cancellationToken)
{
logger.LogInformation($"Service {nameof(HostedService1 )} started.");
return Task.CompletedTask;
}
public Task StartingAsync(CancellationToken cancellationToken)
{
logger.LogInformation($"Service {nameof(HostedService1 )} starting.");
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
logger.LogInformation($"Service {nameof(HostedService1 )} stop.");
return Task.CompletedTask;
}
public Task StoppedAsync(CancellationToken cancellationToken)
{
logger.LogInformation($"Service {nameof(HostedService1 )} stopped.");
return Task.CompletedTask;
}
public Task StoppingAsync(CancellationToken cancellationToken)
{
logger.LogInformation($"Service {nameof(HostedService1 )} stopping.");
return Task.CompletedTask;
}
}
public class HostedService2(ILogger<HostedService2> logger) : IHostedLifecycleService
{
public Task StartAsync(CancellationToken cancellationToken)
{
logger.LogInformation($"Service {nameof(HostedService2)} start.");
return Task.CompletedTask;
}
public Task StartedAsync(CancellationToken cancellationToken)
{
logger.LogInformation($"Service {nameof(HostedService2)} started.");
return Task.CompletedTask;
}
public Task StartingAsync(CancellationToken cancellationToken)
{
logger.LogInformation($"Service {nameof(HostedService2)} starting.");
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
logger.LogInformation($"Service {nameof(HostedService2)} stop.");
return Task.CompletedTask;
}
public Task StoppedAsync(CancellationToken cancellationToken)
{
logger.LogInformation($"Service {nameof(HostedService2)} stopped.");
return Task.CompletedTask;
}
public Task StoppingAsync(CancellationToken cancellationToken)
{
logger.LogInformation($"Service {nameof(HostedService2)} stopping.");
return Task.CompletedTask;
}
}
在上面代码中,我们在每个方法里都打印了服务名和方法名,接着我们将这两个类注册为托管服务并运行应用程序:
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<HostedService1>();
builder.Services.AddHostedService<HostedService2>();
var app = builder.Build();
app.Run();
上面代码中,我们使用 AddHostedService<THostedService>
方法在 DI 容器中注册托管服务。 运行程序,将看到如下输出:
在输出的信息中,我们可以看到方法的调用顺序与预期一致。
三、当前托管服务的启动和停止
在默认情况下,应用会按顺序执行托管服务的启动和停止, 也就是说HostedService2
的 StartingAsync
方法只在HostedService1
的相同方法返回之后执行。但是,从 .NET 8 开始我们可以通过设置ServiceStartConcurrently
和 ServiceStopConcurrently
来改变这种行为,代码如下:
builder.Services.Configure<HostOptions>(options =>
{
options.ServicesStartConcurrently = true;
options.ServicesStopConcurrently = true;
});
设置为 true 时,所有托管服务将同时启动和停止,但是在所有服务的当前生命周期返回之前,任何服务的生命周期都不会启动。 简单说就是所有的 StartingAsync
方法将首先并行执行,然后StartAsync
方法也将并行执行,依此类推。
Tip:当我们的应用程序拥有多个托管服务时,并发启动可缩短启动和关闭的时间。
四、总结
通过 IHostedLifecycleService
接口,我们可以更精细地控制每个服务在生命周期阶段启动和停止时发生的情况,可以初始化其他服务所需的依赖关系。 如果我们的应用程序实现了多个托管服务,就需要引入HostOptions
来同时启动和停止服务。