周期性后台任务-同步版本
所有周期性后台任务需要实现 以下任意一个 抽象类:
- BackgroundTask
- AsyncBackgroundTask
BackgroundTask
BackgroundTask 是一个基类,当我们在项目中实现它的子类时,就能实现【后台周期性任务】,
AsyncBackgroundTask 是对应的异步版本。
它的主要公开成员如下:
/// <summary>
/// 表示一个后台运行的任务,它的子类会在程序启动时自动创建并运行
/// </summary>
public abstract class BackgroundTask
{
/// <summary>
/// 是否需要在每次执行Execute方法时自动生成日志(OprLog + InvokeLog)
/// </summary>
protected bool EnableLog => true;
/// <summary>
/// true表示需要在启动任务线程后立即执行一次,false表示启动后等到到达执行周期才会执行。
/// 默认值:false
/// </summary>
public virtual bool FirstRun => false;
/// <summary>
/// 获取休眠秒数,用于描述“周期任务”的间隔时间,例如:每5秒执行一次
/// 注意:同步版本不支持时间跨度太久的休眠间隔。
/// </summary>
public virtual int? SleepSeconds {
get => null;
}
/// <summary>
/// 获取一个Cron表达式,用于描述“周期任务”的间隔时间。
/// 这里使用的是 Quartz 支持的 Cron 格式,在线工具:https://www.pppet.net/
/// 注意:同步版本不支持时间跨度太久的休眠间隔。
/// </summary>
public virtual string CronValue {
get => null;
}
/// <summary>
/// 执行任务前的初始化。
/// 说明:执行当前方法时框架不做异常处理,如果产生异常会导致进程崩溃。
/// </summary>
/// <returns>如果 return false, 表示初始化失败,将中止任务</returns>
public virtual bool Init()
/// <summary>
/// 执行任务的主体过程。
/// </summary>
public abstract void Execute();
/// <summary>
/// 异常处理方法。
/// 默认行为:如果启用日志就不做任何处理,否则输出到Console
/// </summary>
/// <param name="ex"></param>
public virtual void OnError(Exception ex)
}
要求&说明
子类的实现要求:
- 类型的可见性必须是 public
- 必须指定执行间隔属性: SleepSeconds 或者 CronValue
补充说明:
- 子类的实例由Nebula在启动时创建并调用(项目中不需要实例化)
- 内部已包含异常处理,如果需要额外的日志请重写OnError方法
BackgroundTask, AsyncBackgroundTask 差异
- AsyncBackgroundTask 是异步版本,使用线程池执行作业
- BackgroundTask 使用单独线程(不使用线程池),任务的及时触发率会更好
- 如果对任务的【及时触发】要求不高,建议使用 AsyncBackgroundTask
- AsyncBackgroundTask 支持较长的作业执行间隔,例如:一个月一次
- BackgroundTask 【不支持】较长的作业执行间隔,建议 仅用于 10 秒内的间隔任务
示例1 - 休眠N秒的定时任务
public class Task1 : BackgroundTask
{
public override int? SleepSeconds => 90;
public override void Execute()
{
Console2.Info("Task1,每隔 90 秒执行一次!");
// do something....
}
}
示例2 - 基于CronValue的定时任务
public class Task2 : BackgroundTask
{
public override string CronValue => "0/10 * * * * ? *";
public override void Execute()
{
Console2.Info("Task2,每隔 10 秒执行一次!");
// do something....
}
}
高级用法
1,线上调整作业执行周期
上面2个示例中,我们使用了【固定】的执行周期,
public override int? SleepSeconds => 90;
public override string CronValue => "0/10 * * * * ? *";
这种做法只是简单,不够灵活:它【不支持】线上调整作业执行周期
推荐做法:
private static readonly int s_sleepSeconds = LocalSettings.GetInt("XXXXXXXXXX_SleepSeconds", 90);
public override int? SleepSeconds => s_sleepSeconds;
- 以后线上需要调整作业的执行周期时,只需要添加一个环境变量就可以了。
- CronValue 也可以采用类似的思路,但是建议用Base64编码
SleepSeconds 的实现还可以更复杂,实现动态的作业执行周期:
- 白天(8:00 -- 20:00 ) 时间段内,作业的执行周期是 60 秒
- 夜间(白天之外的时间)时间段内,作业的执行周期是 120 秒
2,作业在程序启动后立即执行,但是延迟一小段时间
public class Task3 : AsyncBackgroundTask
{
public override int? SleepSeconds => 90;
public override bool FirstRun => true; // 作业在程序启动后立即执行
public override async Task ExecuteAsync()
{
if( this.ExecuteCount == 1 ) { // 第一次运行时,延迟一段时间
await Task.Delay( (new Random()).Next(10, 30) * 1000 );
}
Console2.Info("Task1,每隔 90 秒执行一次!");
// do something....
}
}
这里使用了2个基类的属性
- FirstRun : 可参考前面的注释描述
- ExecuteCount :它指示当前 Execute/ExecuteAsync 是第几次运行