[Abp 源码分析]十六、后台作业与后台工作者 (2)

最后肯定也有相关的销毁方法,用于释放所有注入的后台工作者对象,并将集合清除。

private bool _isDisposed; public void Dispose() { if (_isDisposed) { return; } _isDisposed = true; // 遍历集合,通过 Ioc 解析器的 Release 方法释放对象 _backgroundJobs.ForEach(_iocResolver.Release); // 清空集合 _backgroundJobs.Clear(); }

所以,针对于所有后台工作者的管理,都是通过 IBackgroundWorkerManager 来进行操作的。

2.1.2 后台工作者

看完了管理器,我们来看一下 IBackgroundWorker 后台工作者对象是怎样的构成。

public interface IBackgroundWorker : IRunnable { }

貌似只是一个空的接口,其作用主要是标识某个类型是否为后台工作者,转到其抽象类实现 BackgroundWorkerBase,里面只是注入了一些辅助对象与本地化的一些方法。

public abstract class BackgroundWorkerBase : RunnableBase, IBackgroundWorker { // 配置管理器 public ISettingManager SettingManager { protected get; set; } // 工作单元管理器 public IUnitOfWorkManager UnitOfWorkManager { get { if (_unitOfWorkManager == null) { throw new AbpException("Must set UnitOfWorkManager before use it."); } return _unitOfWorkManager; } set { _unitOfWorkManager = value; } } private IUnitOfWorkManager _unitOfWorkManager; // 获得当前的工作单元 protected IActiveUnitOfWork CurrentUnitOfWork { get { return UnitOfWorkManager.Current; } } // 本地化资源管理器 public ILocalizationManager LocalizationManager { protected get; set; } // 默认的本地化资源的源名称 protected string LocalizationSourceName { get; set; } protected ILocalizationSource LocalizationSource { get { // 如果没有配置源名称,直接抛出异常 if (LocalizationSourceName == null) { throw new AbpException("Must set LocalizationSourceName before, in order to get LocalizationSource"); } if (_localizationSource == null || _localizationSource.Name != LocalizationSourceName) { _localizationSource = LocalizationManager.GetSource(LocalizationSourceName); } return _localizationSource; } } private ILocalizationSource _localizationSource; // 日志记录器 public ILogger Logger { protected get; set; } protected BackgroundWorkerBase() { Logger = NullLogger.Instance; LocalizationManager = NullLocalizationManager.Instance; } // ... 其他模板代码 }

我们接着看继承并实现了 BackgroundWorkerBase 的类型 PeriodicBackgroundWorkerBase,从字面意思上来看,该类型应该是一个定时后台工作者基类。

重点在于 Periodic(定时),从其类型内部的定义可以看到,该类型使用了一个 AbpTimer 对象来进行周期计时与具体工作任务的触发。我们暂时先不看这个 AbpTimer,仅仅看 PeriodicBackgroundWorkerBase 的内部实现。

public abstract class PeriodicBackgroundWorkerBase : BackgroundWorkerBase { protected readonly AbpTimer Timer; // 注入 AbpTimer protected PeriodicBackgroundWorkerBase(AbpTimer timer) { Timer = timer; // 绑定周期执行的任务,这里是 DoWork() Timer.Elapsed += Timer_Elapsed; } public override void Start() { base.Start(); Timer.Start(); } public override void Stop() { Timer.Stop(); base.Stop(); } public override void WaitToStop() { Timer.WaitToStop(); base.WaitToStop(); } private void Timer_Elapsed(object sender, System.EventArgs e) { try { DoWork(); } catch (Exception ex) { Logger.Warn(ex.ToString(), ex); } } protected abstract void DoWork(); }

可以看到,这里基类绑定了 DoWork() 作为其定时执行的方法,那么用户在使用的时候直接继承自该基类,然后重写 DoWork() 方法即可绑定自己的后台工作者的任务。

2.1.3 AbpTimer 定时器

在上面的基类我们看到,基类的 Start()、Stop()、WaitTpStop() 方法都是调用的 AbpTimer 所提供的,所以说 AbpTimer 其实也继承了 RunableBase 基类并实现其具体的启动与停止操作。

其实 AbpTimer 的核心就是通过 CLR 的 Timer 来实现周期性任务执行的,不过默认的 Timer 类有两个比较大的问题。

CLR 的 Timer 并不会等待你的任务执行完再执行下一个周期的任务,如果你的某个任务耗时过长,超过了 Timer 定义的周期。那么 Timer 会开启一个新的线程执行,这样的话最后我们系统的资源会因为线程大量重复创建而被拖垮。

如何知道一个 Timer 所执行的业务方法已经真正地被结束了。

所以 Abp 才会重新封装一个 AbpTimer 作为一个基础的计时器。第一个问题的解决方法很简单,就是在执行具体绑定的业务方法之前,通过 Timer.Change() 方法来让 Timer 临时失效。等待业务方法执行完成之后,再将 Timer 的周期置为用户设定的周期。

// CLR Timer 绑定的回调方法 private void TimerCallBack(object state) { lock (_taskTimer) { if (!_running || _performingTasks) { return; } // 暂时让 Timer 失效 _taskTimer.Change(Timeout.Infinite, Timeout.Infinite); // 设置执行标识为 TRUE,表示当前的 AbpTimer 正在执行 _performingTasks = true; } try { // 如果绑定了相应的触发事件 if (Elapsed != null) { // 执行相应的业务方法,这里就是最开始绑定的 DoWork() 方法 Elapsed(this, new EventArgs()); } } catch { } finally { lock (_taskTimer) { // 标识业务方法执行完成 _performingTasks = false; if (_running) { // 更改周期为用户指定的执行周期,等待下一次触发 _taskTimer.Change(Period, Timeout.Infinite); } Monitor.Pulse(_taskTimer); } } }

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

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