在某些时候我们可能会需要执行后台任务,或者是执行一些周期性的任务。比如说可能每隔 1 个小时要清除某个临时文件夹内的数据,可能用户会要针对某一个用户群来群发一组短信。前面这些就是典型的应用场景,在 Abp 框架里面为我们准备了后台作业和后台工作者来帮助我们解决这个问题。
后台作业与后台工作者的区别是,前者主要用于某些耗时较长的任务,而不想阻塞用户的时候所使用。后者主要用于周期性的执行某些任务,从 “工作者” 的名字可以看出来,就是一个个工人,而且他们每个工人都拥有单独的后台线程。
0.1 典型场景后台作业
某个用户按下了报表按钮来生成一个需要长时间等待的报表。你添加这个工作到队列中,当报表生成完毕后,发送报表结果到该用户的邮箱。
在后台作业中发送一封邮件,有些问题可能会导致发送失败(网络连接异常,或者主机宕机);由于有后台作业以及持久化机制,在问题排除后,可以重试以保证任务的成功执行。
后台工作者
后台工作者能够周期性地执行旧日志的删除。
后台工作者可以周期性地筛选出非活跃性用户,并且发送回归邮件给这些用户。
1. 启动流程后台作业与后台工作者都是通过各自的 Manager(IBackgroundJobManager/IBackgroundWorkerManager) 来进行管理的。而这两个 Manager 分别继承了 ISingletonDependency 接口,所以在启动的时候就会自动注入这两个管理器以便开发人员管理操作。
这里值得注意的一点是,IBackgroundJobManager 接口是 IBackgroundWorker 的派生接口,而 IBackgroudWorker 是归属于 IBackgroundWorkerManager 进行管理的。
所以,你可以在 AbpKernelModule 里面看到如下代码:
public sealed class AbpKernelModule : AbpModule { public override void PostInitialize() { // 注册可能缺少的组件 RegisterMissingComponents(); // ... 忽略的代码 // 各种管理器的初始化操作 // 从配置项中读取,是否启用了后台作业功能 if (Configuration.BackgroundJobs.IsJobExecutionEnabled) { var workerManager = IocManager.Resolve<IBackgroundWorkerManager>(); // 开始启动后台工作者 workerManager.Start(); // 增加后台作业管理器 workerManager.Add(IocManager.Resolve<IBackgroundJobManager>()); } } }可以看到,后台作业管理器是作为一个后台工作者被添加到了 IBackgroundWorkerManager 当中来执行的。
2. 代码分析 2.1 后台工作者 2.1.1 后台工作者管理器Abp 通过后台工作者管理器来管理后台作业队列,所以我们首先来看一下后台工作者管理器接口的定义是什么样子的。
public interface IBackgroundWorkerManager : IRunnable { void Add(IBackgroundWorker worker); }还是相当简洁的,就一个 Add 方法用来添加一个新的后台工作者对象。只是在这个地方,可以看到该接口又是集成自 IRunnable 接口,那么该接口的作用又是什么呢?
转到其定义可以看到,IRunable 接口定义了三个基本的方法:Start()、Stop()、WaitStop() ,而且他拥有一个默认实现 RunableBase,其实就是用来标识一个任务的运行状态。
public interface IRunnable { // 开始执行任务 void Start(); // 停止执行任务 void Stop(); // 阻塞线程,等待任务执行完成后标识为停止。 void WaitToStop(); } public abstract class RunnableBase : IRunnable { // 用于标识任务是否运行的布尔值变量 public bool IsRunning { get { return _isRunning; } } private volatile bool _isRunning; // 启动之后表示任务正在运行 public virtual void Start() { _isRunning = true; } // 停止之后表示任务结束运行 public virtual void Stop() { _isRunning = false; } public virtual void WaitToStop() { } }到目前为止整个代码都还是比较简单清晰的,我们接着看 IBackgroundWorkerManager 的默认实现 BackgroundWorkerManager 类,首先我们看一下该类拥有哪些属性与字段。
在后台工作者管理器类的内部,默认有一个 List 集合,用于维护所有的后台工作者对象。那么其他的 Start() 等方法肯定是基于这个集合进行操作的。
public override void Start() { base.Start(); _backgroundJobs.ForEach(job => job.Start()); } public override void Stop() { _backgroundJobs.ForEach(job => job.Stop()); base.Stop(); } public override void WaitToStop() { _backgroundJobs.ForEach(job => job.WaitToStop()); base.WaitToStop(); }可以看到实现还是比较简单的,接下来我们继续看他的 Add() 方法是如何进行操作的?
public void Add(IBackgroundWorker worker) { _backgroundJobs.Add(worker); if (IsRunning) { worker.Start(); } }在这里我们看到他会针对 IsRunning 进行判定是否立即启动加入的后台工作者对象。而这个 IsRunning 属性值唯一产生变化的情况就在于 Start() 方法与 Stop() 方法的调用。