相关重要的组件一览

Triggers(触发器)相关类

  • 保存触发器相关参数,例如起止时间,次数,间隔时间等,其中Sundial支持多种类型触发器
  • 多种类型的触发器必须重写GetNextOccurrence方法,用于返回下一个触发时间

CronTrigger

  • 引用了第三方包TimeCrontab 3.2.1

  • 构造函数

    //支持三种方式的触发器构建
    public CronTrigger(String schedule, object args){
    // 处理 int 转 CronStringFormat
    /** int 从0开始
    public enum CronStringFormat
    {
    Default,
    WithYears,
    WithSeconds,
    WithSecondsAndYears
    }
    **/
    if (args is int formatValue)
    {
    // 强转int变为CronStringFormat
    Crontab = Crontab.Parse(schedule, (CronStringFormat)formatValue);
    }
    // 处理 CronStringFormat
    else if (args is CronStringFormat format)
    {
    Crontab = Crontab.Parse(schedule, format);
    }
    // 处理 Macro At
    // fields 为数组形式的cron,可用string.Join(",", fields.Select((object f) => f.ToString()).ToArray());解析
    /**
    schedule一般为:
    case "@secondly":
    return SecondlyAt(fields);
    case "@minutely":
    return MinutelyAt(fields);
    case "@hourly":
    return HourlyAt(fields);
    case "@daily":
    return DailyAt(fields);
    case "@monthly":
    return MonthlyAt(fields);
    case "@weekly":
    return WeeklyAt(fields);
    case "@yearly":
    return YearlyAt(fields);
    **/
    else if (args is object[] fields)
    {
    Crontab = Crontab.ParseAt(schedule, fields);
    }
    else throw new NotImplementedException();
    }
  • GetNextOccurrence

    • 直接使用了Crontab.GetNextOccurrence(startAt)获得下次触发时间

PeriodTrigger

  • 构造函数

    • 时间单位为毫秒,最小计数为100ms
  • GetNextOccurrence
    • 每次只需要在当前时间加上设置的毫秒数即可
    • startAt.AddMilliseconds(Interval)

Trigger

  • Trigger主要分为了属性类和方法类两个文件(Trigger.cs和Trigger.Methods.cs)

  • 属性类中主要包含了Trigger的相关属性信息,例如触发时间,次数等基础信息

  • 方法类内容如下

    /**
    虚函数,每个继承Trigger的类都要实现该方法
    **/
    // 下一个触发的时间
    public virtual DateTime GetNextOccurrence(DateTime startAt) => throw new NotImplementedException();
    // 执行检查条件
    public virtual bool ShouldRun(JobDetail jobDetail, DateTime startAt)
    {
    // 下次运行时间不能晚于当前时间且最近执行的时间不能是下次执行事件
    return NextRunTime.Value <= startAt
    && LastRunTime != NextRunTime;
    }
    /// <summary>
    /// 记录运行信息和计算下一个触发时间
    /// </summary>
    /// <param name="jobDetail">作业信息</param>
    /// <param name="startAt">当前时间</param>
    internal void Increment(JobDetail jobDetail, DateTime startAt)
    {
    // 阻塞状态并没有实际执行,此时忽略次数递增和最近运行时间赋值
    if (Status != TriggerStatus.Blocked)
    {
    //触发次数再加一
    NumberOfRuns++;
    // 最近一次的执行时间为下次执行事件
    LastRunTime = NextRunTime;
    }
    // 根据当前时间判断下次执行时间
    NextRunTime = GetNextRunTime(startAt); // 检查下一次执行信息
    CheckAndFixNextOccurrence(jobDetail);
    }
    // 计算下一次运行时间
    // internal DateTime? GetNextRunTime(DateTime startAt)
    // 相关数据转换为sql的方法 ConvertToSQL

Triggers 静态类

主要用于创建TriggerBuilder(作业触发器构建器)

  • 创建TriggerBuilder

    • 创建时间间隔的触发器:Create(interval)

    • 创建Cron的触发器: Create(schedule, CronStringFormat.Default)

      // 创建TriggerBuilder
      public static TriggerBuilder Create<TTrigger>(params object[] args)
      where TTrigger : Trigger
      {
      return Create<TTrigger>().SetArgs(args);
      }
      // 最底层的构造参数
      public static TriggerBuilder Create(Type triggerType)
      {
      // TriggerBuilder方法的相关信息参见TriggerBuilder类说明
      return new TriggerBuilder()
      .SetTriggerType(triggerType)
      .Appended();
      }
      // 设置作业触发器参数
      public TriggerBuilder SetArgs(params object[] args)
      {
      Args = args == null || args.Length == 0
      ? null
      : Penetrates.Serialize(args);
      RuntimeTriggerArgs = args;
      // 返回当前的TriggerBuilder
      return this;
      }
  • TriggerBuilder与Trigger,JSon的相互转换

    • 参见TriggerBuilder类的From方法

TriggerBuilder

  • 触发器构建器

    • PeriodTrigger
    • CronTrigger
  • 触发器转换器

    • From

      //Trigger 转 TriggerBuilder
      /**
      1.通过 typeof(TriggerBuilder).GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) 创建实例
      2.如果失败则用Activator.CreateInstance<TTarget>()创建
      3.获得TriggerBuilder属性targetType.GetProperties(bindFlags)
      4.获得trigger的相关属性和数值
      5.赋值,各种命名方式的进行碰撞,有符和的就赋值
      // 多种属性命名解析
      var propertyName = property.Name;
      var camelCasePropertyName = Penetrates.GetNaming(propertyName, NamingConventions.CamelCase);
      var pascalPropertyName = Penetrates.GetNaming(propertyName, NamingConventions.Pascal);
      var underScoreCasePropertyName = Penetrates.GetNaming(propertyName, NamingConventions.UnderScoreCase);
      // 穷举方式获取值
      object value;
      if (sourcePropertyValues.ContainsKey(propertyName)) value = sourcePropertyValues[propertyName];
      else if (sourcePropertyValues.ContainsKey(camelCasePropertyName)) value = sourcePropertyValues[camelCasePropertyName];
      else if (sourcePropertyValues.ContainsKey(pascalPropertyName)) value = sourcePropertyValues[pascalPropertyName];
      else if (sourcePropertyValues.ContainsKey(underScoreCasePropertyName)) value = sourcePropertyValues[underScoreCasePropertyName];
      else continue; // 忽略空值控制
      if (ignoreNullValue && value == null) continue; property.SetValue(target, value);
      **/
      var triggerBuilder = trigger.MapTo<TriggerBuilder>(); // 初始化运行时作业触发器类型和参数
      triggerBuilder.SetTriggerType(triggerBuilder.AssemblyName,triggerBuilder.TriggerType).
      SetArgs(triggerBuilder.Args);
      // 持久化的型为变更为更新
      return triggerBuilder.Updated();
    • Trigger的JSON转换为TriggerBuilder,多了一步Penetrates.Deserialize(json)---底层调用为:JsonSerializer.Deserialize(json, GetDefaultJsonSerializerOptions())

      /// <summary>
      /// 获取默认的序列化对象
      /// </summary>
      /// <returns><see cref="JsonSerializerOptions"/></returns>
      internal static JsonSerializerOptions GetDefaultJsonSerializerOptions()
      {
      var jsonSerializerOptions = new JsonSerializerOptions
      {
      PropertyNameCaseInsensitive = true,
      ReadCommentHandling = JsonCommentHandling.Skip,
      Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
      AllowTrailingCommas = true
      };
      // 处理时间类型
      jsonSerializerOptions.Converters.Add(new DateTimeJsonConverter());
      return jsonSerializerOptions;
      }
    • 设置作业的基础信息

      • SetTriggerId 设置触发器id

      • SetDescription 设置描述信息

      • SetStatus 设置触发器状态

      • SetStartTime SetEndTime 设置起始和结束时间

      • 设置作业触发器类型

        // 不做 null 检查
        if (triggerType == null) return this; // 检查 triggerType 类型是否派生自 Trigger
        if (!typeof(Trigger).IsAssignableFrom(triggerType)
        // 是否是Trigger类型
        || triggerType == typeof(Trigger)
        // 是否是接口
        || triggerType.IsInterface
        // 是否是抽象类
        || triggerType.IsAbstract) throw new InvalidOperationException("The <triggerType> is not a valid Trigger type."); // 最多只能包含一个构造函数
        if (triggerType.GetConstructors().Length > 1) throw new InvalidOperationException("The <triggerType> can contain at most one constructor.");
        //作业触发器类型所在程序集
        AssemblyName = triggerType.Assembly.GetName().Name;
        // 作业触发器类型
        TriggerType = triggerType.FullName;
        // 作业触发器运行时类型
        RuntimeTriggerType = triggerType;
        return this;

Scheduler(作业计划) 相关类

  • IScheduler 作业计划接口

    • Get方法---返回相关实例:SchedulerModel,SchedulerBuilder,JobBuilder,TriggerBuilder,JobDetail等
    • TryGet方法--尝试查找相关实例:ScheduleResult枚举(不存在,已存在,成功,失败,未找到)
    • 对作业触发器的操作:Add,Update , Remove
    • 对作业的操作:Persist,Start,Pause,Collate,Reload
  • Scheduler 作业计划
    • 作业id,作业组名称
    • JobDetail,IJob,ISchedulerFactory,IScheduleLogger,ILogger
  • Scheduler.Methods 作业计划的方法
    • IScheduler 方法的实现
  • SchedulerModel 作业计划模型

Job(作业信息)相关类

  • IJob 作业处理程序

    • 自身业务继承的接口
    • 具体处理逻辑在ExecuteAsync方法写出
  • JobDetail 作业类

    • 作业的属性
  • JobBuilder 作业信息构建器(继承JobDetail)

    • 创建JobBuilder

      AssemblyName = assemblyName;
      JobType = jobTypeFullName; // 只有 assemblyName 和 jobTypeFullName 同时存在才创建类型
      if (!string.IsNullOrWhiteSpace(assemblyName)
      && !string.IsNullOrWhiteSpace(jobTypeFullName))
      {
      // 加载 GAC 全局应用程序缓存中的程序集及类型
      var jobType = Assembly.Load(assemblyName)
      .GetType(jobTypeFullName);
      return SetJobType(jobType);
      }
      return this;
  • JobDetail.Methods 相关方法

  • JobDetailOptions 配置选项

  • IJobExecutor 作业处理程序执行器

    • 调度作业服务提供了 IJobExecutor 执行器接口,可以让开发者自定义作业处理函数执行策略,如 超时控制,失败重试等等

      public class YourJobExecutor : IJobExecutor
      {
      private readonly ILogger<YourJobExecutor> _logger;
      public YourJobExecutor(ILogger<YourJobExecutor> logger)
      {
      _logger = logger;
      } public async Task ExecuteAsync(JobExecutingContext context, IJob jobHandler, CancellationToken stoppingToken)
      {
      // 实现失败重试策略,如失败重试 3 次
      await Retry.InvokeAsync(async () =>
      {
      await jobHandler.ExecuteAsync(context, stoppingToken);
      }, 3, 1000
      // 每次重试输出日志
      , retryAction: (total, times) =>
      {
      _logger.LogWarning("Retrying {current}/{times} times for {context}", times, total, context);
      });
      }
      } 接着模拟 MyJob 执行出错:
      public class MyJob : IJob
      {
      private readonly ILogger<MyJob> _logger;
      public MyJob(ILogger<MyJob> logger)
      {
      _logger = logger;
      } public Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
      {
      _logger.LogInformation($"{context}"); throw new Exception("模拟出错");
      return Task.CompletedTask;
      }
      } 最后,在注册 Schedule 服务中注册 YourJobExecutor:
      services.AddSchedule(options =>
      {
      // 添加作业执行器
      options.AddExecutor<YourJobExecutor>();
      });

代码及相关资料

  1. Sundial-v2.5.6源码
  2. Sundial帮助文档

Sundial (二)的更多相关文章

  1. 【小程序分享篇 二 】web在线踢人小程序,维持用户只能在一个台电脑持登录状态

    最近离职了, 突然记起来还一个小功能没做, 想想也挺简单,留下代码和思路给同事做个参考. 换工作心里挺忐忑, 对未来也充满了憧憬与担忧.(虽然已是老人, 换了N次工作了,但每次心里都和忐忑). 写写代 ...

  2. 前端开发中SEO的十二条总结

    一. 合理使用title, description, keywords二. 合理使用h1 - h6, h1标签的权重很高, 注意使用频率三. 列表代码使用ul, 重要文字使用strong标签四. 图片 ...

  3. 【疯狂造轮子-iOS】JSON转Model系列之二

    [疯狂造轮子-iOS]JSON转Model系列之二 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇<[疯狂造轮子-iOS]JSON转Model系列之一> ...

  4. 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新

    上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...

  5. 谈谈一些有趣的CSS题目(十二)-- 你该知道的字体 font-family

    开本系列,谈谈一些有趣的 CSS 题目,题目类型天马行空,想到什么说什么,不仅为了拓宽一下解决问题的思路,更涉及一些容易忽视的 CSS 细节. 解题不考虑兼容性,题目天马行空,想到什么说什么,如果解题 ...

  6. MIP改造常见问题二十问

    在MIP推出后,我们收到了很多站长的疑问和顾虑.我们将所有疑问和顾虑归纳为以下二十个问题,希望对大家理解 MIP 有帮助. 1.MIP 化后对其他搜索引擎抓取收录以及 SEO 的影响如何? 答:在原页 ...

  7. 如何一步一步用DDD设计一个电商网站(二)—— 项目架构

    阅读目录 前言 六边形架构 终于开始建项目了 DDD中的3个臭皮匠 CQRS(Command Query Responsibility Segregation) 结语 一.前言 上一篇我们讲了DDD的 ...

  8. ASP.NET Core 之 Identity 入门(二)

    前言 在 上篇文章 中讲了关于 Identity 需要了解的单词以及相对应的几个知识点,并且知道了Identity处在整个登入流程中的位置,本篇主要是在 .NET 整个认证系统中比较重要的一个环节,就 ...

  9. MVVM模式和在WPF中的实现(二)数据绑定

    MVVM模式解析和在WPF中的实现(二) 数据绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...

  10. Key/Value之王Memcached初探:二、Memcached在.Net中的基本操作

    一.Memcached ClientLib For .Net 首先,不得不说,许多语言都实现了连接Memcached的客户端,其中以Perl.PHP为主. 仅仅memcached网站上列出的语言就有: ...

随机推荐

  1. numpy常用知识点备忘

    常用函数 a.max(axis=0) a.max(axis=1) a.argmax(axis=1) : 每列的最大值(在行方向找最大值).每行的最大值(在列方向找对大致).最大值的坐标 sum()求和 ...

  2. DTSE Tech Talk | 第9期:EiPaaS驱动企业数字化转型

    摘要: 揭秘华为企业集成新模式. 本期直播详解 组装式概念解析 EiPaaS的核心技术能力 华为实践经验分享 EiPaaS未来的技术趋势 直播讲师:华为云PaaS DTSE布道师 傅翌伟 tips:E ...

  3. 2022春每日一题:Day 27

    题目:友好城市 分析一下可以转化为:选取最多的点对,使得点对之间连线没有交点,没有交点说明什么,假设选定第i组,则对于任意的j,一定满足a[i].l<a[j].l && a[i] ...

  4. JSP利用AJAX实现页面即时校验验证码

    在JSP页面实现验证码校验文章中当时是使用的Servlet类来进行的验证码校验,但是这种方式并不能即时校验,在正常情况下都是直接在用户输入之后就进行校验,这样对用户来说很方便的. AJAX 即&quo ...

  5. os模块、sys模块、json模块、json模块实战

    目录 os模块 创建目录(文件夹) 删除目录(文件夹) 列举指定路径下内容名称 删除/重命名文件 获取/切换当前工作目录 动态获取项目根路径(重要) 判断路径是否存在(文件.目录) 路径拼接(重要) ...

  6. 【Java SE进阶】Day02 Collection、Iterator、泛型

    一.Collection集合 1.概述 数组存元素,集合存对象(类型可以不一样) 2.框架分类 单列:Collection List ArrayList LinkedList Set HashSet ...

  7. vue设计与实现 第6章 ref 响应原理 笔记

    ref 函数实现代码 const a = ref(1); function ref(value){ const wrapper = {value} Object.defineProperty(wrap ...

  8. Java/JDK各版本主要特性汇总

    目录 Java18(2022.3) Java17(2021.9)(LTS版本) Java16(2021.3) Java15(2020.9) Java14(2020.3) Java13(2019.9) ...

  9. 用 Java?试试国产轻量的 Solon v1.11.4(带视频)

    一个更现代感的 Java 应用开发框架:更快.更小.更自由.没有 Spring,没有 Servlet,没有 JavaEE:独立的轻量生态.主框架仅 0.1 MB. @Controller public ...

  10. 可视化编排的数据集成和分发开源框架Nifi轻松入门-上

    @ 目录 概述 定义 dataflow面临挑战 特性 核心概念 架构 高级概述 安装 部署 常见处理器 入门示例 概述 定义 Nifi 官网地址 https://nifi.apache.org/ Ni ...