.net core 下 Quartz 任务调度的使用和配置 

Quarzt 是一个开源的作业调度框架;允许程序开发人员根据时间间隔来调度作业;实现了任务和触发器的多对多关系.

1.Quarzt 核心概念:
  • Job : 表示一个工作,要执行的具体内容; 接口中定义了一个方法 void excute(JobExecutionContext context);
  • JobDetail:  表示一个具体的可执行的调度程序;包含了这个任务调度的方案和策略.
  • Trigger:  一个调度参数的配置,配置调用的时间和周期.框架提供了5种触发器类型(SimpleTrigger、CronTrigger、DateIntervalTrigger、 NthlncludedDayTrigger、Calendar类).
  • Scheduler: 一个调度容器可以注册多个JobDetail和Trigger.调度需要组合JobDetail和Trigger;

附: 常用的trigger为SimpleTrigger和CronTrigger. 
SimpleTrigger 执行N次,重复N次;
CronTrigger: 几秒 几分 几时 哪日 哪月 哪周 哪年执行

quartz表达式在线网址:  http://cron.qqe2.com/

1.1 Quarzt 运行环境:

可以嵌入在应用程序,也可以作为一个独立的程序运行.

1.2.Quarzt 存储方式:
  • RAMJobStore(内存作业存储类型)和JDBCJobStore(数据库作业存储类型),两种方式对比如下:
优点 缺点
RAMJobStore 不要外部数据库,配置容易,运行速度快 因为调度程序信息是存储在被分配给JVM的内存里面, 所以,当应用程序停止运行时,所有调度信息将被丢失。另外因为存储到JVM内存里面,所以可以存储多少个Job和Trigger将会受到限制
BCJobStor 支持集群,因为所有的任务信息都会保存 运行速度的快慢取决与连接数据库的快慢到数据库中,可以控制事物,还有就是如果应用服务器关闭或者重启,任务信息都不会丢失,并且可以恢复因服务器关闭或者重启而导致执行失败的任务
2. Quarzt 在.net的实现
2.1 Quarzt ISchedulerCenter接口定义
  • ISchedulerCenter接口主要定义了以下几个接口

开启任务调度 StartScheduleAsync()
停止任务调度 StopScheduleAsync()
添加一个任务 AddScheduleJobAsync(TasksQz sysSchedule)
停止一个任务 StopScheduleJobAsync(TasksQz sysSchedule)
恢复一个任务 ResumeJob(TasksQz sysSchedule)

    /// <summary>
    /// 服务调度接口
    /// </summary>
    public interface ISchedulerCenter
    {

        /// <summary>
        /// 开启任务调度
        /// </summary>
        /// <returns></returns>
        Task<MessageModel<string>> StartScheduleAsync();

        /// <summary>
        /// 停止任务调度
        /// </summary>
        /// <returns></returns>
        Task<MessageModel<string>> StopScheduleAsync();

        /// <summary>
        /// 添加一个任务
        /// </summary>
        /// <returns></returns>
        Task<MessageModel<string>> AddScheduleJobAsync(TasksQz sysSchedule);

       
        /// <summary>
        /// 停止一个任务
        /// </summary>
        /// <returns></returns>
        Task<MessageModel<string>> StopScheduleJobAsync(TasksQz sysSchedule);

        /// <summary>
        /// 恢复一个任务
        /// </summary>
        /// <returns></returns>
        Task<MessageModel<string>> ResumeJob(TasksQz sysSchedule);

    }



2.2 Quarzt 任务调度服务中心 SchedulerCenterServer 封装
  • 任务调度中心 SchedulerCenterServer 类的实现, 继承实现 ISchedulerCenter 接口;包含任务的开启、任务的关闭方法;以及对指定任务的开启、暂停和恢复方法的封装;

    /// <summary>
    /// 任务调度管理中心
    /// </summary>
    public class SchedulerCenterServer : ISchedulerCenter
    {
        private Task<IScheduler> _scheduler;
        private readonly IJobFactory _iocjobFactory;

        public SchedulerCenterServer(IJobFactory jobFactory)
        {
            _iocjobFactory = jobFactory;
            _scheduler = GetSchedulerAsync();
        }

        private Task<IScheduler> GetSchedulerAsync()
        {
            if (_scheduler != null)
                return this._scheduler;
            else
            {
                //从Factory中获取Scheduler实例
                NameValueCollection collection = new NameValueCollection
                {
                    {"quartz.serializer.type","binary" }
                };
                StdSchedulerFactory factory = new StdSchedulerFactory(collection);
                return _scheduler = factory.GetScheduler();
            }
        }


        /// <summary>
        /// 开启任务调度
        /// </summary>
        /// <returns></returns>
        public async Task<MessageModel<string>> StartScheduleAsync()
        {
            var result = new MessageModel<string>();
            try
            {
                this._scheduler.Result.JobFactory = this._iocjobFactory;
                if (!this._scheduler.Result.IsStarted)
                {
                    //等待任务运行完成
                    await this._scheduler.Result.Start();
                    await Console.Out.WriteLineAsync("任务调度开启!");
                    result.success = true;
                    result.msg = $"任务调度开启成功";
                    return result;
                }
                else
                {
                    result.success = false;
                    result.msg = $"任务调度已经开启";
                    return result;
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            throw new NotImplementedException();
        }

        /// <summary>
        /// 停止任务调度
        /// </summary>
        /// <returns></returns>
        public async Task<MessageModel<string>> StopScheduleAsync()
        {
            var result = new MessageModel<string>();
            try
            {
                if (!this._scheduler.Result.IsShutdown)
                {
                    //等待任务运行完成
                    await this._scheduler.Result.Shutdown();
                    await Console.Out.WriteLineAsync("任务调度停止! ");

                    result.success = true;
                    result.msg = $"任务调度停止成功";
                    return result;
                }
                else
                {
                    result.success = false;
                    result.msg = $"任务调度已经停止";
                    return result;
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            throw new NotImplementedException();
        }




        /// <summary>
        /// 添加一个计划任务(映射程序集指定IJob实现类)
        /// </summary>
        /// <param name="sysSchedule"></param>
        /// <returns></returns>
        public async Task<MessageModel<string>> AddScheduleJobAsync(TasksQz sysSchedule)
        {
            var result = new MessageModel<string>();
            if (sysSchedule != null)
            {
                try
                {
                    JobKey jobKey = new JobKey(sysSchedule.Id.ToString());
                    if (await _scheduler.Result.CheckExists(jobKey))
                    {
                        result.success = false;
                        result.msg = $"该任务计划已经在执行:【{sysSchedule.Name}】";
                        return result;
                    }
                    #region 设置开始时间和结束时间
                    if (sysSchedule.BeginTime == null)
                    {
                        sysSchedule.BeginTime = DateTime.Now;
                    }
                    DateTimeOffset startRunTime = DateBuilder.NextGivenSecondDate(sysSchedule.BeginTime, 1);//设置开始时间
                    if (sysSchedule.EndTime == null)
                    {
                        sysSchedule.EndTime = DateTime.MaxValue.AddDays(-1);
                    }
                    DateTimeOffset endRunTime = DateBuilder.NextGivenSecondDate(sysSchedule.EndTime, 1);//设置暂停时间

                    #endregion

                    #region 通过反射获取程序集类型和类

                    Assembly assembly = Assembly.Load(sysSchedule.AssemblyName);
                    Type jobType = assembly.GetType(sysSchedule.AssemblyName + "." + sysSchedule.ClassName);

                    #endregion

                    //判断任务调度是否开启
                    if (!_scheduler.Result.IsStarted)
                    {
                        await StartScheduleAsync();
                    }

                    //传入反射出来的执行程序集
                    IJobDetail job = new JobDetailImpl(sysSchedule.Id.ToString(), sysSchedule.JobGroup, jobType);
                    job.JobDataMap.Add("JobParam", sysSchedule.JobParams);
                    ITrigger trigger;
                    #region 泛型传递
                    //IJobDetail job = JobBuilder.Create<T>()
                    //    .WithIdentity(sysSchedule.Name, sysSchedule.JobGroup)
                    //    .Build();
                    #endregion
                    if (sysSchedule.Cron != null && CronExpression.IsValidExpression(sysSchedule.Cron) && sysSchedule.TriggerType > 0)
                    {
                        trigger = CreateCronTrigger(sysSchedule);
                    }
                    else
                    {
                        trigger = CreateSimpleTrigger(sysSchedule);
                    }

                    //告诉Quartz使用我们的触发器来安排作业
                    await _scheduler.Result.ScheduleJob(job, trigger);

                    //await Task.Delay(TimeSpan.FromSeconds(120));
                    //await Console.Out.WriteLineAsync("关闭了调度器!");
                    //await _scheduler.Result.Shutdown();

                    result.success = true;
                    result.msg = $"启动任务: 【{sysSchedule.Name}】成功";
                    return result;

                }
                catch (Exception ex)
                {
                    result.success = false;
                    result.msg = $"任务计划异常:【{ex.Message}】";
                    return result;
                }
            }
            else
            {
                result.success = false;
                result.msg = $"任务计划不存在:【{sysSchedule?.Name}】";
                return result;
            }
        }

        /// <summary>
        /// 暂停一个指定的计划任务
        /// </summary>
        /// <param name="sysSchedule"></param>
        /// <returns></returns>
        public async Task<MessageModel<string>> StopScheduleJobAsync(TasksQz sysSchedule)
        {
            var result = new MessageModel<string>();
            try
            {
                JobKey jobKey = new JobKey(sysSchedule.Id.ToString(), sysSchedule.JobGroup);
                if (!await _scheduler.Result.CheckExists(jobKey))
                {
                    result.success = false;
                    result.msg = $"未找到要暂停的任务:【{sysSchedule.Name}】";
                    return result;
                }
                else
                {
                    await this._scheduler.Result.PauseJob(jobKey);
                    result.success = true;
                    result.msg = $"暂停任务:【{sysSchedule.Name}】成功";
                    return result;
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }


        /// <summary>
        /// 恢复指定的计划任务
        /// </summary>
        /// <param name="sysSchedule"></param>
        /// <returns></returns>
        public async Task<MessageModel<string>> ResumeJob(TasksQz sysSchedule)
        {
            var result = new MessageModel<string>();
            try
            {
                JobKey jobKey = new JobKey(sysSchedule.Id.ToString(), sysSchedule.JobGroup);
                if (!await _scheduler.Result.CheckExists(jobKey))
                {
                    result.success = false;
                    result.msg = $"未找到要重新运行的任务,【{sysSchedule.Name}】";
                    return result;
                }
                //await this._scheduler.Result.ResumeJob(jobKey);

                ITrigger trigger;
                if (sysSchedule.Cron != null && CronExpression.IsValidExpression(sysSchedule.Cron) && sysSchedule.TriggerType > 0)
                {
                    trigger = CreateCronTrigger(sysSchedule);
                }
                else
                {
                    trigger = CreateSimpleTrigger(sysSchedule);
                }
                TriggerKey triggerKey = new TriggerKey(sysSchedule.Id.ToString(), sysSchedule.JobGroup); ;
                await _scheduler.Result.RescheduleJob(triggerKey, trigger);

                result.success = true;
                result.msg = $"恢复计划任务: 【{sysSchedule.Name}】 成功";
                return result;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }


        #region 创建触发器帮助方法

        private ITrigger CreateSimpleTrigger(TasksQz sysSchedule)
        {
            if (sysSchedule.RunTimes > 0)
            {
                ITrigger trigger = TriggerBuilder.Create()
                    .WithIdentity(sysSchedule.Id.ToString(), sysSchedule.JobGroup)
                    .StartAt(sysSchedule.BeginTime.Value)
                    .EndAt(sysSchedule.EndTime.Value)
                    .WithSimpleSchedule(x =>
                    x.WithIntervalInSeconds(sysSchedule.IntervalSecond)
                    .WithRepeatCount(sysSchedule.RunTimes)).ForJob(sysSchedule.Id.ToString(), sysSchedule.JobGroup).Build();
                return trigger;
            }
            else
            {
                ITrigger trigger = TriggerBuilder.Create()
                    .WithIdentity(sysSchedule.Id.ToString(), sysSchedule.JobGroup)
                    .StartAt(sysSchedule.BeginTime.Value)
                    .EndAt(sysSchedule.EndTime.Value)
                    .WithSimpleSchedule(x =>
                    x.WithIntervalInSeconds(sysSchedule.IntervalSecond)
                    .RepeatForever()).ForJob(sysSchedule.Id.ToString(), sysSchedule.JobGroup).Build();
                return trigger;
            }
            //触发作业立即运行,然后每10秒重复一次,无限循环
        }


        /// <summary>
        /// 创建类型Cron的触发器
        /// </summary>
        /// <param name="sysSchedule"></param>
        /// <returns></returns>
        private ITrigger CreateCronTrigger(TasksQz sysSchedule)
        {
            //作业触发器
            return TriggerBuilder.Create()
                .WithIdentity(sysSchedule.Id.ToString(), sysSchedule.JobGroup)
                .StartAt(sysSchedule.BeginTime.Value)
                .EndAt(sysSchedule.EndTime.Value)
                .WithCronSchedule(sysSchedule.Cron)//指定cron表达式
                .ForJob(sysSchedule.Id.ToString(), sysSchedule.JobGroup)
                .Build();
        }
        #endregion
    }

2.3 Job工厂类 JobFactory 实现:
public class JobFactory : IJobFactory
{
        private readonly IServiceProvider _serviceProvider;  

        public JobFactory(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }



        /// <summary>
        /// 实现接口Job
        /// </summary>
        /// <param name="bundle"></param>
        /// <param name="scheduler"></param>
        /// <returns></returns>
        public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
        {
            try
            {

                var serviceScope = _serviceProvider.CreateScope();

                var job = serviceScope.ServiceProvider.GetService(bundle.JobDetail.JobType) as IJob;

                return job;

            }
            catch(Exception ex)
            {
                throw ex;
            }

        }


        public void ReturnJob(IJob job)
        {

            var disposable = job as IDisposable;

            if(disposable != null)
            {
                disposable.Dispose();
            }

        }
    }

2.4 单个任务调度的示例 :
  • Job_Users_Quartz 这里是要执行的服务和方法 比如每隔一段时间对平台用户的总数量进行统计
    public class Job_Users_Quartz : JobBase, IJob
    {

        private readonly ITasksQzServices _tasksQzServices;

        private readonly IUserServices _userServices;

        private readonly ILogger<Job_Users_Quartz> _logger;

        public Job_Users_Quartz(ITasksQzServices tasksQzServices, IUserServices userServices, ILogger<Job_Users_Quartz> logger)
        {

            _tasksQzServices = tasksQzServices;
            _userServices = userServices;
            _logger = logger;

        }



        public async Task Execute(IJobExecutionContext context)
        {

            //var param = context.MergedJobDataMap;
            // 可以直接获取 JobDetail 的值
            var jobKey = context.JobDetail.Key;
            var jobId = jobKey.Name;

            var executeLog = await ExecuteJob(context, async () => await Run(context, jobId.ObjToInt()));

            //通过数据库配置,获取传递过来的参数
            JobDataMap data = context.JobDetail.JobDataMap;
      
        }
        
        public async Task Run(IJobExecutionContext context, int jobid)
        {

            var count = await _userServices.QueryCount(x=>x.ID > 0);
            if (jobid > 0)
            {

                var model = await _tasksQzServices.QueryById(jobid);
                if (model != null)
                {

                    model.RunTimes += 1;

                    var separator = "<br>";

                    string remark = $"【{DateTime.Now}】执行任务【Id:{context.JobDetail.Key.Name},组别:context.JobDetail.Key.Group}】【执行成功】{separator}";

                    model.Remark = remark + string.Join(separator, StringHelper.GetTopDataBySeparator(model.Remark, separator, 3));

                    //_logger.LogInformation(remark);
                    await _tasksQzServices.Update(model);
                }
            }
            //_logger.LogInformation("用户总数量" + count.ToString());
            await Console.Out.WriteLineAsync("用户总数量" + count.ToString());
        }
    }
2.5 Quartz 启动服务中间件
    /// <summary>
    /// Quartz 启动服务
    /// </summary>
    public static class QuartzJobMildd
    {

        public static void UseQuartzJobMildd(this IApplicationBuilder app, ITasksQzServices tasksQzServices, ISchedulerCenter schedulerCenter)
        {

            if (app == null) throw new 
ArgumentNullException(nameof(QuartzJobMildd));


            try
            {

                if (Appsettings.app("Middleware", "QuartzNetJob", "Enabled").ObjToBool())
                {

                    var allQzServices = tasksQzServices.Query().Result;

                    foreach (var item in allQzServices)
                    {

                        if (item.IsStart)
                        {

                            var resuleModel = schedulerCenter.AddScheduleJobAsync(item).Result;

                            if (resuleModel.success)
                            {
                                Console.WriteLine($"QuartzNetJob{item.Name}启动成功! ");
                            }
                            else
                            {
                                Console.WriteLine($"QuartzNetJob{item.Name}启动失败! 错误信息{resuleModel.msg}");
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {

                Console.WriteLine($"An error was reported when starting the job service.\n{ex.Message}");
                throw;
            }
        }
    }

2.6 任务调度启动服务 JobSetup
    /// <summary>
    /// 任务调度 启动服务
    /// </summary>
    public static class JobSetup
    {

        public static void AddJobSetup(this IServiceCollection services)
        {

            if (services == null) throw new ArgumentNullException(nameof(services));

            //services.AddHostedService<Job1TimedService>();
            //services.AddHostedService<Job2TimedService>();

            services.AddSingleton<IJobFactory, JobFactory>();
            services.AddTransient<Job_Users_Quartz>();//Job使用瞬时依赖注入
            services.AddSingleton<ISchedulerCenter, SchedulerCenterServer>();

        }

    }


2.7 Quartz 中间件和服务在Startup的的启用
 public void ConfigureServices(IServiceCollection services)
{
 services.AddJobSetup();
}


 
  public void Configure(IApplicationBuilder app, IWebHostEnvironment env, 
ITasksQzServices tasksQzServices, ISchedulerCenter schedulerCenter)
 {
      
 //// 开启QuartzNetJob调度服务
app.UseQuartzJobMildd(tasksQzServices, schedulerCenter);

}

2.8 Quartz 的 TasksQz 类定义
    /// <summary>
    /// 任务计划表
    /// </summary>

    public class TasksQz 
    {
         /// <summary>
        /// ID
        /// </summary>
        [SugarColumn(IsNullable = false,IsPrimaryKey = true,IsIdentity = true)]
        public int Id { get; set; }

        /// <summary>
        /// 任务名称
        /// </summary>
        [SugarColumn(ColumnDataType = "nvarchar",Length = 200,IsNullable = true)]
        public string Name { get; set;}

        /// <summary>
        /// 任务分组
        /// </summary>
        [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)]
        public string JobGroup { get; set; }

        /// <summary>
        /// 任务运行时间表达式
        /// </summary>
        [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)]
        public string Cron { get; set; }

        /// <summary>
        /// 任务所在DLL对应的程序集名称
        /// </summary>
        [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)]
        public string AssemblyName { get; set; }

        /// <summary>
        /// 任务所在类
        /// </summary>
        [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)]
        public string ClassName { get; set; }

        /// <summary>
        /// 任务描述
        /// </summary>
        [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)]
        public string Remark { get; set; }  


        /// <summary>
        /// 执行次数
        /// </summary>
        public int RunTimes { get; set; }

        /// <summary>
        /// 开始时间
        /// </summary>
        public DateTime? BeginTime { get; set; }

        /// <summary>
        /// 结束时间
        /// </summary>
        public DateTime? EndTime { get; set; }

        /// <summary>
        ///  触发器类型(0 simple  1 cron)
        /// </summary>
        public int TriggerType { get; set; }



        /// <summary>
        /// 执行间隔时间,秒为单位
        /// </summary>
        public int IntervalSecond { get; set; }

        /// <summary>
        /// 是否启动
        /// </summary>
        public bool IsStart { get; set; }


        /// <summary>
        /// 执行传参
        /// </summary>
        public string JobParams { get; set; }


        /// <summary>
        /// 是否删除 1 删除 
        /// </summary>
        [SugarColumn(IsNullable = true)]
        public bool? IsDeleted { get; set; }

        /// <summary>
        /// 创建时间
        /// </summary>
        [SugarColumn(IsNullable = true)]
        public DateTime CreateTime { get; set; } = DateTime.Now;

    }

参考源码:
https://github.com/anjoy8/Blog.Core

Quartz管理工具:
https://github.com/guryanovev/crystalquartz

posted @ 2022-01-02 16:49  云水边静沐暖阳丶  阅读(1932)  评论(1编辑  收藏  举报