后台任务和计划作业是可能需要在任何应用程序中使用的内容,无论它是否遵循微服务体系结构模式。 使用微服务体系结构时的差异在于,可以在单独的进程/容器中实现后台任务进行托管,以便可以根据需求将其缩减/纵向扩展。
从一般的角度来看,在 .NET 中,我们调用了此类任务 托管服务,因为它们是托管在主机/应用程序/微服务中的服务/逻辑。 请注意,在这种情况下,托管的服务只是指一个包含后台任务逻辑的类。
由于 .NET Core 2.0,该框架提供了一个名为 IHostedService 帮助轻松实现托管服务的新接口。 基本思路是,可以在 Web 主机或主机运行时注册在后台运行的多个后台任务(托管服务),如图 6-26 所示。
图 6-26. 在 WebHost 与主机中使用 IHostedService
ASP.NET Core 1.x 和 2.x 支持在 Web 应用中运行后台进程IWebHost
。 .NET Core 2.1 及更高版本支持在纯控制台应用中使用 IHost
进行后台进程。 请注意WebHost
和Host
之间的区别。
WebHost
ASP.NET Core 2.0 中的(基类实现IWebHost
)是用于向进程提供 HTTP 服务器功能的基础结构项目,例如,在实现 MVC Web 应用或 Web API 服务时。 它提供了 ASP.NET Core 中的所有新基础结构良好性,使你能够使用依赖项注入、在请求管道中插入中间件,以及类似。
WebHost
将这些相同的 IHostedServices
用于后台任务。
.NET Core 2.1 中引入了一个 Host
(基类实现 IHost
)。 基本上,Host
可以让你拥有类似于 WebHost
的基础结构(例如依赖注入、托管服务等),但在这种情况下,你只需要一个简单且轻量的过程作为主机,不涉及MVC、Web API或HTTP服务器功能。
因此,可以选择并创建一个专用的主机进程 IHost
来处理托管服务,而不需要处理其他任何服务,例如用于托管的 IHostedServices
微服务,或者也可以扩展现有的 ASP.NET Core WebHost
,例如现有的 ASP.NET Core Web API 或 MVC 应用。
每个方法都有优点和缺点,具体取决于业务和可伸缩性需求。 底线基本上是,如果后台任务与 HTTP (IWebHost
) 无关,则应使用 IHost
。
在 WebHost 或主机中注册托管服务
让我们更深入地钻研IHostedService
接口,因为在WebHost
或Host
中,这个接口的使用方式非常相似。
SignalR 是使用托管服务的项目的一个示例,但也可以将其用于更简单的事情,例如:
- 轮询数据库以查找更改的后台任务。
- 定期更新某些缓存的计划任务。
- QueueBackgroundWorkItem 的实现,允许在后台线程上执行任务。
- 在共享常见服务(例如
ILogger
)时,在 Web 应用后台处理来自消息队列的消息。 - 从
Task.Run()
开始的后台任务。
基本上可以将这些操作中的任何一个转移到实现IHostedService
的后台任务中。
向 IHostedServices
或 WebHost
添加一个或多个 Host
的方式是,通过 ASP.NET Core AddHostedService(或 .NET Core 2.1 及更高版本中的 WebHost
)中的 Host
扩展方法对它们进行注册。 基本上,必须在应用程序启动中注册 Program.cs中的托管服务。
//Other DI registrations;
// Register Hosted Services
builder.Services.AddHostedService<GracePeriodManagerService>();
builder.Services.AddHostedService<MyHostedServiceB>();
builder.Services.AddHostedService<MyHostedServiceC>();
//...
在该代码中, GracePeriodManagerService
托管服务是 eShopOnContainers 中 Ordering 业务微服务的真实代码,而另外两个只是另外两个示例。
IHostedService
后台任务的执行与应用程序(就此而言,为主机或微服务)的生存期相协调。 在应用程序启动时注册任务,并有机会在应用程序关闭时执行一些优雅的动作或清理。
如果不使用 IHostedService
,始终可以启动后台线程来运行任何任务。 应用关闭时的区别在于,当时该线程会被直接终止,没有机会执行正常的清理操作。
IHostedService 接口
注册 IHostedService
时,.NET 会在应用程序启动和停止期间分别调用 StartAsync()
类型的 StopAsync()
和 IHostedService
方法。 有关更多详细信息,请参阅 IHostedService 接口。
可以想象一下,可以创建 IHostedService 的多个实现,并在 Program.cs中注册其中每个实现,如前所示。 所有这些托管服务将与应用程序/微服务一起启动和停止。
作为开发人员,当主机触发 StopAsync()
方法时,需负责处理服务的停止操作。
使用派生自 BackgroundService 基类的自定义托管服务类实现 IHostedService
可以继续从头开始创建自定义托管服务类并实现IHostedService
,就像在使用 .NET Core 2.0及更高版本时需要做的一样。
但是,由于大多数后台任务在取消令牌管理和其他常见操作方面具有类似的需求,因此,你可以从一个命名为 BackgroundService
的方便的抽象基类派生(自 .NET Core 2.1 起可用)。
该类提供设置后台任务所需的主要工作。
下一个代码是在 .NET 中实现的抽象 BackgroundService 基类。
// Copyright (c) .NET Foundation. Licensed under the Apache License, Version 2.0.
/// <summary>
/// Base class for implementing a long running <see cref="IHostedService"/>.
/// </summary>
public abstract class BackgroundService : IHostedService, IDisposable
{
private Task _executingTask;
private readonly CancellationTokenSource _stoppingCts =
new CancellationTokenSource();
protected abstract Task ExecuteAsync(CancellationToken stoppingToken);
public virtual Task StartAsync(CancellationToken cancellationToken)
{
// Store the task we're executing
_executingTask = ExecuteAsync(_stoppingCts.Token);
// If the task is completed then return it,
// this will bubble cancellation and failure to the caller
if (_executingTask.IsCompleted)
{
return _executingTask;
}
// Otherwise it's running
return Task.CompletedTask;
}
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
// Stop called without start
if (_executingTask == null)
{
return;
}
try
{
// Signal cancellation to the executing method
_stoppingCts.Cancel();
}
finally
{
// Wait until the task completes or the stop token triggers
await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite,
cancellationToken));
}
}
public virtual void Dispose()
{
_stoppingCts.Cancel();
}
}
从已存在的抽象基类派生时,由于继承的实现,只需在你自己的托管服务类中实现ExecuteAsync()
方法,如以下 eShopOnContainers 简化代码所示,该代码会轮询数据库并根据需要将集成事件发布到事件总线。
public class GracePeriodManagerService : BackgroundService
{
private readonly ILogger<GracePeriodManagerService> _logger;
private readonly OrderingBackgroundSettings _settings;
private readonly IEventBus _eventBus;
public GracePeriodManagerService(IOptions<OrderingBackgroundSettings> settings,
IEventBus eventBus,
ILogger<GracePeriodManagerService> logger)
{
// Constructor's parameters validations...
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogDebug($"GracePeriodManagerService is starting.");
stoppingToken.Register(() =>
_logger.LogDebug($" GracePeriod background task is stopping."));
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogDebug($"GracePeriod task doing background work.");
// This eShopOnContainers method is querying a database table
// and publishing events into the Event Bus (RabbitMQ / ServiceBus)
CheckConfirmedGracePeriodOrders();
try {
await Task.Delay(_settings.CheckUpdateTime, stoppingToken);
}
catch (TaskCanceledException exception) {
_logger.LogCritical(exception, "TaskCanceledException Error", exception.Message);
}
}
_logger.LogDebug($"GracePeriod background task is stopping.");
}
.../...
}
在 eShopOnContainers 的这个特定场景中,它正在执行一个应用程序方法,该方法查询数据库表以查找具有特定状态的订单。当应用更改时,它通过事件总线发布集成事件(底层可能使用 RabbitMQ 或 Azure 服务总线)。
当然,也可以改为运行任何其他业务的后台任务。
默认情况下,取消令牌设置为 5 秒超时,虽然在构建WebHost
时,你可以使用UseShutdownTimeout
的IWebHostBuilder
扩展来更改该值。 这意味着我们的服务预计将在 5 秒内取消,否则会更突然地终止。
以下代码会将该时间更改为 10 秒。
WebHost.CreateDefaultBuilder(args)
.UseShutdownTimeout(TimeSpan.FromSeconds(10))
...
摘要类图
下图显示了实现 IHostedServices 时涉及的类和接口的可视化摘要。
图 6-27. 显示与 IHostedService 相关的多个类和接口的类关系图
类图:IWebHost 和 IHost 可以托管许多服务,这些服务继承自实现 IHostedService 的 BackgroundService。
部署注意事项和要点
请务必注意,部署 ASP.NET Core WebHost
或 .NET Host
的方式可能会影响最终解决方案。 例如,如果在 IIS 或常规 Azure 应用服务上部署 WebHost
,由于应用池回收,主机可能会被关闭。 但是,如果要将主机作为容器部署到 Kubernetes 等调度器中,则可以控制主机的保证存活实例数量。 此外,您可以考虑云中的其他方法,特别是专门为这些场景制作的方法,例如 Azure Functions。 最后,如果需要服务一直运行并在 Windows Server 上部署,则可以使用 Windows 服务。
但即使对于部署到应用池中的 WebHost
,也存在如重新填充或刷新应用程序的内存中缓存这样的情况,这仍然适用。
该 IHostedService
接口提供了一种便捷的方法,用于在 ASP.NET Core Web 应用程序(在 .NET Core 2.0 及更高版本中)或任何进程/主机(从 .NET Core 2.1 开始) IHost
中启动后台任务。 其主要优势在于,当主机本身将要关闭时,可以有机会进行正常取消以清理后台任务的代码。
其他资源
在 ASP.NET Core/Standard 2.0 中生成计划任务
https://blog.maartenballiauw.be/post/2017/08/01/building-a-scheduled-cache-updater-in-aspnet-core-2.html在 ASP.NET Core 2.0 中实现 IHostedService
https://www.stevejgordon.co.uk/asp-net-core-2-ihostedservice使用 ASP.NET Core 2.1 的 GenericHost 示例
https://github.com/aspnet/Hosting/tree/release/2.1/samples/GenericHostSample