周期性后台任务-同步版本

所有周期性后台任务需要实现 以下任意一个 抽象类:

  • 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 是第几次运行