相关重要的组件一览

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. Springboot数据库的配置问题

    mysql时区问题 先前的代码如下 spring: datasource: url: jdbc:mysql://localhost:3306/test username: root password: ...

  2. webpack -- element-ui 的按需引入

    简单说明原理: 使用babel-plugin-component实现按需引入.打包.将webpack配置成多入口,保证最终打包的目录结构符合babel-plugin-component插件的要求,实现 ...

  3. 记一次node节点无法加入K8S集群

    #问题现象:root@small-virtual-machine:~# kubeadm join 10.0.0.133:6443 --token d2hyl5.5qt5fzjsdbxm2k5o     ...

  4. vulnhub靶场之DEATHNOTE: 1

    准备: 攻击机:虚拟机kali.本机win10. 靶机:DEATHNOTE: 1,网段地址我这里设置的桥接,所以与本机电脑在同一网段,下载地址:https://download.vulnhub.com ...

  5. .NET复习总纲

    以下是自己学习遇到比较好的课程和学习网站,如果大家有更好的课程推荐,可以打在评论区或者私聊我,让我也进行学习和补充进文档 一..NET基础 官方文档:https://learn.microsoft.c ...

  6. 你认识的C# foreach语法糖,真的是全部吗?

    本文的知识点其实由golang知名的for循环陷阱发散而来, 对应到我的主力语言C#, 其实牵涉到闭包.foreach.为了便于理解,我重新组织了语言,以倒叙结构行文. 先给大家提炼出一个C#题:观察 ...

  7. Froms

    首先看到的是一个输入框 不多说,直接bp抓下来 然后传repeater里,发现了pin值后showsource值,pin值没什么,应该是做题用的,而showsource是个隐藏的值,将其0改为1后go ...

  8. Linux 使用打印机

    前言 在 deepin 上打印机好使,在我的mint上不好使,简单的查看一下deepin上驱动及软件.安装上就行了. 软件及驱动 ii hpijs-ppds 3.18.12+dfsg0-2 all H ...

  9. 【每日一题】【(双端)队列初始化&工具类&层次遍历】2022年1月29日-NC14 按之字形顺序打印二叉树

    描述给定一个二叉树,返回该二叉树的之字形层序遍历,(第一层从左向右,下一层从右向左,一直这样交替) 注意:树的初始化 public class TreeNode { int val = 0; Tree ...

  10. android nativate 动态注册 静态注册

    说明:在java函数的入口比较容易分析, 把activity的生命周期或者关键函数通过放在so层,分析起来就困难多了 1.在MainActivity中 package com.demo.nativat ...