Asp.Net Core 轻松学-基于微服务的后台任务调度管理器

    在 Asp.Net Core 中,我们常常使用 System.Threading.Timer 这个定时器去做一些需要长期在后台运行的任务,但是这个定时器在某些场合却不太灵光,而且常常无法控制启动和停止,我们需要一个稳定的,类似 WebHost 这样主机级别的任务管理程序,但是又要比 WebHost 要轻便。

    由此,我找到了官方推荐的 IHostedService 接口,该接口位于程序集 Microsoft.Extensions.Hosting.Abstractions 的 命名空间 Microsoft.Extensions.Hosting。该接口自 .Net Core 2.0 开始提供,按照官方的说法,由于该接口的出现,下面的这些应用场景的代码都可以删除了。

历史场景列表

轮询数据库以查找更改的后台任务

从 Task.Run() 开始的后台任务

定期更新某些缓存的计划任务

允许任务在后台线程上执行的 QueueBackgroundWorkItem 实现

在 Web 应用后台处理消息队列中的消息,同时共享 ILogger 等公共服务

1. 原理解释

1.1 首先来看接口 IHostedService 的代码,这需要花一点时间去理解它的原理,你也可以跳过本段直接进入第二段

namespace Microsoft.Extensions.Hosting { // // Summary: // Defines methods for objects that are managed by the host. public interface IHostedService { // // Summary: // Triggered when the application host is ready to start the service. Task StartAsync(CancellationToken cancellationToken); // // Summary: // Triggered when the application host is performing a graceful shutdown. Task StopAsync(CancellationToken cancellationToken); } }

1.2 非常简单,只有两个方法,但是非常重要,这两个方法分别用于程序启动和退出的时候调用,这和 Timer 有着云泥之别,这是质变。

1.3 从看到 IHostedService 这个接口开始,我就习惯性的想,按照微软的惯例,某个接口必然有其默认实现的抽象类,然后我就看到了 Microsoft.Extensions.Hosting.BackgroundService ,果然,前人种树后人乘凉,在 BackgroundService 类中,接口已经实现好了,我们只需要去实现 ExecuteAsync 方法

1.4 BackgroundService 内部代码如下,值得注意的是 BackgroundService 从 .Net Core 2.1 开始提供,所以,使用旧版本的同学们可能需要升级一下

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(); } }

1.5 BackgroundService 内部实现了 IHostedService 和 IDisposable 接口,从代码实现可以看出,BackgroundService 充分实现了任务启动注册和退出清理的逻辑,并保证在任务进入 GC 的时候及时的退出,这很重要。

2. 开始使用

2.1 首先创一个通用的任务管理类 BackManagerService ,该类继承自 BackgroundService

public class BackManagerService : BackgroundService { BackManagerOptions options = new BackManagerOptions(); public BackManagerService(Action<BackManagerOptions> options) { options.Invoke(this.options); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { // 延迟启动 await Task.Delay(this.options.CheckTime, stoppingToken); options.OnHandler(0, $"正在启动托管服务 [{this.options.Name}]...."); stoppingToken.Register(() => { options.OnHandler(1, $"托管服务 [{this.options.Name}] 已经停止"); }); int count = 0; while (!stoppingToken.IsCancellationRequested) { count++; options.OnHandler(1, $" [{this.options.Name}] 第 {count} 次执行任务...."); try { options?.Callback(); if (count == 3) throw new Exception("模拟业务报错"); } catch (Exception ex) { options.OnHandler(2, $" [{this.options.Name}] 执行托管服务出错", ex); } await Task.Delay(this.options.CheckTime, stoppingToken); } } public override Task StopAsync(CancellationToken cancellationToken) { options.OnHandler(3, $" [{this.options.Name}] 由于进程退出,正在执行清理工作"); return base.StopAsync(cancellationToken); } }

BackManagerService 类继承了 BackgroundService ,并实现了 ExecuteAsync(CancellationToken stoppingToken) 方法,在 ExecuteAsync 方法内,先是延迟启动任务,接下来进行注册和调度,这里使用 while 循环判断如果令牌没有取消,则一直轮询,而轮询的关键在于下面的代码

protected override async Task ExecuteAsync(CancellationToken stoppingToken) { ... while (!stoppingToken.IsCancellationRequested) { ... await Task.Delay(this.options.CheckTime, stoppingToken); } }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zysyss.html