相关重要的组件一览

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. Python基础部分:11、文件和光标移动

    目录 一.文件操作 1.文件的概念 2.代码打开文件的方式 二.文件读写模式 1.'r' 只读模式 read 2.'w' 只写模式 write 3.'a' 尾部追写模式 add 三.文件操作模式 1. ...

  2. The Google File System 翻译和理解

    The Google File System 摘要 GFS 是一个可扩展的分布式文件系统,用于大型分布式数据密集型应用上.它可以运行在便宜的普通硬件上,提供了高性能和一定的容错性. 1. 分布式文件系 ...

  3. DevOps|乱谈开源社区、开源项目与企业内部开源

    之前的一篇文章<从特拉斯辞职风波到研发效能中的荒唐事>中关于企业内源的内容在研发效能群内引起了大家的热烈讨论.有的小伙伴不同意,有的小伙伴非常不同意,我觉得这都是非常正常的反馈,话不说不透 ...

  4. Appscan的安装破解以及使用

    本文简单介绍Appscan的安装和使用. 一.下载安装 可自行百度下载相关安装包(因较高版本的破解资料比较难找,建议下载9.0版本). 双击.exe安装文件进行安装,在弹出的安装指引中各选项默认安装即 ...

  5. .NET应用开发之SQLServer常见问题分析

    日常我们开发.NET应用时会使用SQLServer数据库,对于SQLServer数据库的日常开发有一些技能和工具,准备给大家分享一下. 一.场景1:SQLServer死锁分析  执行以下SQL,启用S ...

  6. linux全新机器环境搭建流程梳理

    软件解压后安装基础指令(复制用):./configure && make && make install ./configure --prefix=/usr/local ...

  7. Qwt开发笔记(一):Qwt简介、下载以及基础demo工程模板

    前言   QWT开发笔记系列整理集合,这是目前使用最为广泛的Qt图表类(Qt的QWidget代码方向只有QtCharts,Qwt,QCustomPlot),使用多年,系统性的整理,本系列旨在系统解说并 ...

  8. 【终极解决办法】pyinstaller打包exe没有错误,运行exe提示Failed to execute script 'mainlmageWindows' due tounhandled exception: No module named 'docx'

    一.通过pyinstaller打包exe可执行文件,由于我的py是多个,所以要先生成spec文件,代码如下: pyi-makespec mainImageWindows.py 此时生产了一个mainI ...

  9. 【每日一题】【哈希表,返回结果的下标】2022年1月18日-NC61 两数之和

    描述给出一个整型数组 numbers 和一个目标值 target,请在数组中找出两个加起来等于目标值的数的下标,返回的下标按升序排列.(注:返回的数组下标从1开始算起) 算法: import java ...

  10. Python 什么是flask框架?快速入门(flask安装,登录,新手三件套,登录认证装饰器,配置文件,路由系统,CBV)

    目录 一:Python flask框架 前言 补充一下,我们前面学习的库都是叫模块,那么框架与库的区别? 二:flask 框架概述 1.简介 2.须知: 3.flask框架的优势 三:flask 安装 ...