FluentScheduler

FluentScheduler

代码布局

  • 代码布局相对简单,没有cron,不建议使用JobFactory

  • 源码布局如下

    第一级 第二级 说明
    Enum(文件夹) 枚举
    TimeOfDayRunnable.cs 判断任务执行状态的枚举
    Week.css 每个月的第几周
    Event(文件夹) 三个触发事件 参见上篇的3.事件监听
    Extension(文件夹) 扩展方法
    DateTimeExtensions.cs 时间的扩展方法
    DelayForExtensions.cs 延迟执行的扩展方法
    RestrictableUnitExtensions.cs 设置下次任务执行时间
    Unit(文件夹) 各种触发器文件夹
    Util(文件夹)
    ScheduleCollection.cs 调度器的操作类
    TimeOfDayRunnableCalculator.cs 判断任务状态(还在,在执行,执行了)
    IJob.cs 任务接口类
    JobFactory.cs 不建议使用
    JobManager.cs 任务管理类,核心类
    Registry.cs 任务调度类
    Schedule.cs 调度类,添加任务和触发器

源码说明

JobManager

  1. 按照官方所给demo,程序最先调用的是Initialize方法

    • 该方法参数可为空,也可以是Registry类的数组,源代码写法为

      // 调用该方法时,如果不含参数也可直接调用,且registries还不为null
      public static void Initialize(params Registry[] registries){
          //如果registries为null则抛出异常
          if (registries == null)
          	throw new ArgumentNullException("registries");
      	// 从自定义的调度器中获得任务并计算任务下次的任务时间,即使执行的统一调用RunJob方法,执行
          CalculateNextRun(registries.SelectMany(r => r.Schedules)).ToList().ForEach(RunJob);
          // 这里的开始值得是任务开始执行
          Start();
      }
      
    • Initialize方法将自定义的调度器中的任务进行RunJob

      • registries.SelectMany(r => r.Schedules) 返回任务列表集合

      • CalculateNextRun 将任务和附加任务(间隔多少单位执行一次)进行yield return

        // 将获得的调度进行循环
        foreach (var schedule in schedules)
        {
        	// 如果下次执行的时间为null
            if (schedule.CalculateNextRun == null)
            {
            	// 如果有延迟执行
                if (schedule.DelayRunFor > TimeSpan.Zero)
                {
                    // 下次执行的时间(+延迟)
                    schedule.NextRun = Now.Add(schedule.DelayRunFor);
                    // 添加
                    _schedules.Add(schedule);
                }
                else
                {
                    // 返回调度,立即执行
                    yield return schedule;
                }
                var hasAdded = false;
                // 如果子任务中有延迟,则进行筛选
                foreach (var child in schedule.AdditionalSchedules.Where(x => x.CalculateNextRun != null))
                {
                	// 设置子调度的延迟时间
                    var nextRun = child.CalculateNextRun(Now.Add(child.DelayRunFor).AddMilliseconds(1));
                    // 上次已添加过,且下次执行时间比延迟的更早
                    if (!hasAdded || schedule.NextRun > nextRun)
                    {
                    	// 没有添加过且下次执行的时间更晚
                        schedule.NextRun = nextRun;
                        hasAdded = true;
                    }
                }
            }
            else
            {
            	// 计算出下次执行时间
                schedule.NextRun = schedule.CalculateNextRun(Now.Add(schedule.DelayRunFor));
                _schedules.Add(schedule);
            }
        
        	// 根据间隔时间添加的子调度进行循环,逻辑同上
            foreach (var childSchedule in schedule.AdditionalSchedules)
            {
                if (childSchedule.CalculateNextRun == null)
                {
                    if (childSchedule.DelayRunFor > TimeSpan.Zero)
                    {
                        // 延迟调度
                        childSchedule.NextRun = Now.Add(childSchedule.DelayRunFor);
                        _schedules.Add(childSchedule);
                    }
                    else
                    {
                        // 返回调度,立即执行
                        yield return childSchedule;
                        continue;
                    }
                }
                else
                {
                    childSchedule.NextRun = childSchedule.CalculateNextRun(Now.Add(childSchedule.DelayRunFor));
                    _schedules.Add(childSchedule);
                }
            }
        }
        
      • RunJob 执行任务

        // 如果调取设置为失效,则返回
        if (schedule.Disabled)
            return;
        lock (_running)
        {
        	// 如果已经引入或引入的相同,则跳出
            if (schedule.Reentrant != null &&
                _running.Any(t => ReferenceEquals(t.Item1.Reentrant, schedule.Reentrant)))
                return;
        }
        Tuple<Schedule, Task> tuple = null;
        var task = new Task(() =>
        {
            var start = Now;
            if (JobStart != null)
            {
            	// 任务开始动作
                JobStart(
                    new JobStartInfo
                    {
                        Name = schedule.Name,
                        StartTime = start,
                    }
                );
            }
            var stopwatch = new Stopwatch();
            try
            {
            	// 监听开始
                stopwatch.Start();
                // 将任务放到Task中
                schedule.Jobs.ForEach(action => Task.Factory.StartNew(action).Wait());
            }
            catch (Exception e)
            {
            	// 报错执行的Exception
                if (JobException != null)
                {
                    var aggregate = e as AggregateException;
        
                    if (aggregate != null && aggregate.InnerExceptions.Count == 1)
                        e = aggregate.InnerExceptions.Single();
        			// 任务出现异常调用的方法
                    JobException(
                        new JobExceptionInfo
                        {
                            Name = schedule.Name,
                            Exception = e,
                        }
                    );
                }
            }
            finally
            {
                lock (_running)
                {
                    _running.Remove(tuple);
                }
        
                if (JobEnd != null)
                {
                    // 任务结束调用的方法
                    JobEnd(
                        new JobEndInfo
                        {
                            Name = schedule.Name,
                            StartTime = start,
                            Duration = stopwatch.Elapsed,
                            NextRun = schedule.NextRun,
                        }
                    );
                }
            }
        }, TaskCreationOptions.PreferFairness);
        tuple = new Tuple<Schedule, Task>(schedule, task);
        lock (_running)
        {
        	// 将任务放到准备执行的类型中
            _running.Add(tuple);
        }
        task.Start();
        
    • Initialize的Start则是开始执行

      // 修改计数器的计数数值
      _timer.Change(Timeout.Infinite, Timeout.Infinite);
      // 排序
      _schedules.Sort();
      // 没有调度任务则返回
      if (!_schedules.Any())
          return;
      // 获取第一个任务
      var firstJob = _schedules.First();
      // 如果第一个任务早于当前时间
      if (firstJob.NextRun <= Now)
      {
      	// 如果NextRun早于当前时间,则可以判断有要马上执行的任务
          RunJob(firstJob);
          // 如果下次执行的时间不为null
          if (firstJob.CalculateNextRun != null)
          {
          	 // 计算出下次要执行的任务时间
               firstJob.NextRun = firstJob.CalculateNextRun(Now.AddMilliseconds(1));
          }
          // 如果下次执行时间依然早于当前时间或者已挂起
          if (firstJob.NextRun <= Now || firstJob.PendingRunOnce)
          {
          	// 移除该任务
              _schedules.Remove(firstJob);
          }
      
      	// 设置为非怪奇
          firstJob.PendingRunOnce = false;
          // 调度任务,继续判断第一个任务的状态,并且通过时间间隔进行下次任务时间的设定
          ScheduleJobs();
          return;
      }
      // 判断要执行的时间
      var interval = firstJob.NextRun - Now;
      if (interval <= TimeSpan.Zero)
      {
      	// 重复之前的操作
          ScheduleJobs();
          return;
      }
      else
      {
          if (interval.TotalMilliseconds > _maxTimerInterval)
              interval = TimeSpan.FromMilliseconds(_maxTimerInterval);
      	// 修改计数器的计数数值
          _timer.Change(interval, interval);
      }
      
    • _timer.Change(Timeout.Infinite, Timeout.Infinite) 停止计数器计数,用于停止任务,stop方法触发

    • AddJob(Action jobSchedule, Schedule schedule) 向任务调度中添加任务,该任务为实时任务。

      JobManager.AddJob(
          () => Logger.Information("Late: added after the initialize"),
          s => s.WithName("Late").ToRunNow()
      );
      // AddJob方法内部为调用
      // jobSchedule(schedule)
      // CalculateNextRun(new Schedule[] { schedule }).ToList().ForEach(RunJob); 
      // ScheduleJobs();
      

Registry

	1.	可重载的注册调度类,在方法初始化时调用,构造函数中编写独立的业务逻辑
   - 好处:业务逻辑做到了低耦合,可单独控制一个Registry类来实现不同业务类型的调度
	2.	Registry代码中都是对于不同参数的构造函数的编写

Schedule

​ - 调度任务类,用于执行任务、对于调度器的把控

posted @ 2023-01-28 17:15  摧残一生  阅读(204)  评论(0编辑  收藏  举报