1 插件概述
- 开源链接:https://gitee.com/dotnetchina/Sundial
- 作者:百小僧
- 版本:2.5.6
2 使用方式
2.1 安装
- nuget :搜索Sundial或使用命令:Install-Package Sundial
- .NET CLI :dotnet add package Sundial
- 本地引用 :直接编译源码,引用
2.2 使用
2.2.1快速入门
-
自定义作业处理类
public class TestJob : IJob{ private readonly ILogger<TestJob> _logger; public MyJob(ILogger<TestJob> logger){ _logger = logger; } public Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken){ _logger.LogInformation($"异步执行:{context}"); return Task.CompletedTask; } }
-
调用自定义类
//在Startup.cs中注册Schedule服务 Host.CreateDefaultBuilder(args) .ConfigureServices(services => { services.AddSchedule(options => { options.AddJob<TestJob>(Triggers.PeriodSeconds(5) , Triggers.Minutely()); }); }) .Build();
2.3 AddJob方法参数说明
-
重载一
-
一个或多个作业触发器
-
场景:作业相互独立,并行
-
参数:AddJob(params TriggerBuilder[] triggerBuilders)
-
参数说明:
- Triggers.Minutely() 每分钟执行一次
- Triggers.Period(1000) 每一秒执行一次
- Triggers.Cron("3,7,8 * * * * ?", CronStringFormat.WithSeconds)) 每分钟第 3/7/8 秒
- 参数为数组,因此可以包含多个触发器
-
举例
// 每5秒和每一分钟执行一次并行的作业 options.AddJob<MyJob>(Triggers.PeriodSeconds(5),Triggers.Minutely());
-
-
重载二
-
并发执行一个或多个作业触发器
-
场景:作业串行,同一时间只有一个作业
-
参数:AddJob(bool concurrent, params TriggerBuilder[] triggerBuilders),默认为并行
-
举例
//并行方式,每秒都会执行,不管上一个作业是否完成 options.AddJob<TestJob>(concurrent: true, Triggers.Secondly()); //串行方式,上一个任务完成才会执行,不一定每秒执行一次 options.AddJob<TestJob>(concurrent: false, Triggers.Secondly());
-
-
重载三
-
指定作业id的一个或多个作业触发器,默认系统会自动生成
-
场景:根据作业id进行业务处理,并行
-
参数:AddJob(string jobId, params TriggerBuilder[] triggerBuilders)
-
举例
// 定义一个叫job的作业,同一时间可能存在多个名为job的作业 options.AddJob<TestJob>("job", Triggers.Secondly());
-
-
重载四
-
指定作业id的一个或多个作业触发器且可以设置为串行
-
场景:根据作业id进行业务处理,串行
-
参数:AddJob(string jobId, bool concurrent, params TriggerBuilder[] triggerBuilders)
-
举例:
// 定义一个叫job的作业,串行,同一时间只有一个名为job的作业 options.AddJob<TestJob>("job", false, Triggers.Secondly());
-
2.4 JobExecutingContext 作业执行的上下文
-
基类 JobExecutionContext
-
属性
属性名 类型 说明 JobId string 作业id TriggerId string 作业触发器 JobDetail JobDetail 作业信息 Trigger Trigger 作业触发器 OccurrenceTime DateTime 作业计划触发时间 -
方法
方法名 参数 说明 ConvertToJSON(NamingConventions naming = NamingConventions.CamelCase) NamingConventions命名转换器 输出完整作业 的JSON ToString() 无 输出作业信息+触发器+计划触发事件+下次执行时间/触发器状态
2.3 作者的文档很全
[参见作者帮助文档](https://furion.baiqian.ltd/docs/job/#2613-%E4%BD%9C%E4%B8%9A%E4%BF%A1%E6%81%AF-jobdetail-%E5%8F%8A%E6%9E%84%E5%BB%BA%E5%99%A8)
3 关于调度器的通用理念
3.1 个人对调度器的理解
进行作业调度,首先要有一个或者多个作业(Job——英文名直译为工作/任务),一般是一个个独立的业务,例如车间通过某个零件使用率来判断是否需要向库房提报备件计划。
那什么时候执行这个任务呢?这就需要一个或者多个触发器(Trigger)来明确什么时候执行作业,触发器可以是一次性的,也可能是通过cron来定时。这样就形成了一个最基本的调度。
3.2 Sundial
-
作业计划 Scheduler
- 包含作业信息,作业触发器,作业处程序的基础信息
- 包含
SchedulerBuilder
和TriggerBuilder
,任务和触发器的实例,可进行修改
-
作业信息 JobDetail
-
通过JobDetail来进行初始化设置,只读类型不可更改
-
JobBuilder为运行时的JobDetial的类型,可直接修改运行时JobDetail数据
-
作业调度模块可提供多种方式创建JobBuilder对象
-
通过Create静态方法创建
// 根据作业 Id 创建 var jobBuilder = JobBuilder.Create("job1"); // 根据 IJob 实现类类型创建 var jobBuilder = JobBuilder.Create<MyJob>(); // 根据程序集名称和类型完全限定名(FullName)创建 var jobBuilder = JobBuilder.Create("YourProject", "YourProject.MyJob"); // 根据 Type 类型创建 var jobBuilder = JobBuilder.Create(typeof(MyJob)); // 通过委托创造动态作业 var jobBuilder = JoBuilder.Create((serviceProvider, context, stoppingToken) => { serviceProvider.GetLogger().LogInformation($"{context}"); return Task.CompletedTask; });
-
通过JobDetail类型创建
var jobBuilder = JobBuilder.From(jobDetail); //也可以通过以下方式 var jobBuilder = jobDetail.GetBuilder();
-
通过LoadForm实例填充当前的JobBuilder
// 会覆盖所有相同的值 jobBuilder.LoadFrom(new { Description = "我是描述", Concurrent = false }); // 支持多个填充,还可以配置跳过 null 值覆盖 jobBuilder.LoadFrom(new { Description = "我是另外一个描述", Concurrent = false, IncludeAnnotations = default(object) // 会跳过赋值 }, ignoreNullValue: true); // 支持忽略特定属性名映射 jobBuilder.LoadFrom(new { Description = "我是另外一个描述", Concurrent = false, IncludeAnnotations = default(object) // 会跳过赋值 }, ignorePropertyNames: new[]{ "description" }); // 支持字典类型 jobBuilder.LoadFrom(new Dictionary<string, object> { {"Description", "这是新的描述" }, {"include_annotations", false }, {"updatedTime", DateTime.Now } });
-
通过注解进行赋值
[JobDetail("jobId")] // 仅作业 Id [JobDetail("jobId", "这是一段描述")] // 描述 [JobDetail("jobId", false)] // 串行 [JobDetail("jobId", false, "这是一段描述")] // 串行 + 描述 [JobDetail("jobId", Concurrent = false, Description = "这是一段描述")] public class MyJob : IJob { // .... }
-
通过JSON字符串创建
var jobBuilder = JobBuilder.From(@"{ ""jobId"": ""job1"", ""groupName"": null, ""jobType"": ""MyJob"", ""assemblyName"": ""ConsoleApp13"", ""description"": null, ""concurrent"": true, ""includeAnnotations"": false, ""properties"": ""{}"", ""updatedTime"": null }");
-
-
-
作业触发器Trigger
-
Trigger在初始化时使用,运行时只读。
-
TriggerBuilder为运行时的实例,可修改监听
-
创建
TriggerStatus
-
通过TriggerBuilder.Create进行创建
-
自定义作业触发器
继承
Trigger
并重写GetNextOccurrence
方法即可public class CustomTrigger : Trigger { public override DateTime GetNextOccurrence(DateTime startAt) { return startAt.AddSeconds(2); } }
调用CustomTrigger触发器
services.AddSchedule(options => { //TriggerBuilder.Create 或 Triggers.Create都可以 options.AddJob<MyJob>(Triggers.Create<CustomTrigger>()); });
-
通过Tirgger类型创建(运行时更新作业触发器)
//通过tigger获得运行的triggerBuilder var triggerBuilder = TriggerBuilder.From(trigger); //直接get triggerBuilder = trigger.GetBuilder();
-
-
-
作业处理程序IJob
-
作业处理类型注册模式,默认为单例模式,非单例模式,可通过
IServiceProvder
创建// 瞬时的非单例模式,执行完毕即销毁 public class MyJob : IJob { private readonly ILogger<MyJob> _logger; private readonly IConfiguration _configuration; //当前的服务环境 private readonly IServiceProvider _serviceProvider; public MyJob(ILogger<MyJob> logger, IConfiguration configuration, IServiceProvider serviceProvider) { _logger = logger; _configuration = configuration; _serviceProvider = serviceProvider; } public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken) { //创建不同的环境对象 using var serviceScope = _serviceProvider.CreateScope(); //在另一个环境对象中进行业务操作 var repository = serviceScope.ServiceProvider.GetService<IRepository<User>>(); _logger.LogInformation($"{context} {_configuration["key"]}"); await Task.CompletedTask; } } // 在该Job中有效的非单例注册模式 public class MyJob : IJob, IDisposable { private readonly ILogger<MyJob> _logger; private readonly IConfiguration _configuration; private readonly IServiceScope _serviceScope; public MyJob(ILogger<MyJob> logger , IConfiguration configuration , IServiceProvider serviceProvider) { _logger = logger; _configuration = configuration; _serviceScope = serviceProvider.CreateScope(); } public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken) { var repository = _serviceScope.ServiceProvider.GetService<IRepository<User>>(); var user = await repository.GetAsync(1); _logger.LogInformation($"{context} {_configuration["key"]}"); await Task.CompletedTask; } public void Dispose() { _serviceScope?.Dispose(); } }
-
-
作业监视器 IJobMonitor
-
可通过serviceProvider.GetService
()获得一个IJobMonitor -
也可自定义一个监视器
public class YourJobMonitor : IJobMonitor { private readonly ILogger<YourJobMonitor> _logger; public YourJobMonitor(ILogger<YourJobMonitor> logger) { _logger = logger; } public Task OnExecutingAsync(JobExecutingContext context, CancellationToken stoppingToken) { _logger.LogInformation("执行之前:{context}", context); return Task.CompletedTask; } public Task OnExecutedAsync(JobExecutedContext context, CancellationToken stoppingToken) { _logger.LogInformation("执行之后:{context}", context); if (context.Exception != null) { _logger.LogError(context.Exception, "执行出错啦:{context}", context); } return Task.CompletedTask; } } //在服务中注册 services.AddSchedule(options => { // 添加作业执行监视器 options.AddMonitor<YourJobMonitor>(); });
-
-
作业执行器 IJobExecutor
-
当任务出现异常时-超时,异常,熔断,可通过作业执行器来定义执行策略
public class YourJobExecutor : IJobExecutor { private readonly ILogger<YourJobExecutor> _logger; public YourJobExecutor(ILogger<YourJobExecutor> logger) { _logger = logger; } //jobHandler 当前实例的句柄 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); }); } } //在服务中注册 services.AddSchedule(options => { // 添加作业执行器 options.AddExecutor<YourJobExecutor>(); });
-