Explain the free and open source DotNet task scheduling component Quartz. NET (. NET component introduction 5)

  • 2021-08-31 07:37:32
  • OfStack

Many software projects will use timed tasks, timed polling database synchronization, timed email notification and other functions. . NET Framework has "built-in" timer functionality via the System. Timers. Timer class. The problems that need to be faced when using Timer class: the timer has no persistence mechanism; The timer has an inflexible schedule (only start time and repetition interval can be set, not based on date, time, etc.); Timers do not use thread pools (1 thread per timer); Timers have no real management scheme-you have to write your own mechanisms so that you can remember, organize, and retrieve task names and so on.

If you need to implement the timer function in. NET, you can try the following open source free component Quartz. Net component. At present, the version of Quartz. NET is 3.0, which modifies the original problems: repairing the scheduler signaling that cannot work together with AdoJobStore due to thread local storage; The local state of the thread is completely deleted; quartz. serializer. type is required even if non-serialized RAMJobStore is in use; JSON serialization is incorrectly called a serialization callback.

1. Quart. NET Overview:

Quartz is a job scheduling system that can be integrated or used with any other software system. The Job Scheduler is a system that executes (or notifies) other software components when the preprocessor is executed-determining (scheduling) the arrival of the time. Quartz is very flexible and contains multiple usage examples, which can be used individually or in one instance to achieve the behavior you need and enable you to write code in the most "natural" way your project looks. Components are very lightweight to use and require very little setup/configuration-if your requirements are relatively basic, it can actually be used "out of the box." Quartz is fault tolerant and can keep (remember) your scheduled job between system reboots. While Quartz is useful for simply running certain system processes on a given schedule, the full potential of Quartz can be realized when you learn how to use Quartz to drive the business processes of your application.

Quartz is distributed as a small dynamic link library (. dll file) that contains all the core Quartz functionality. The main interface for this feature (API) is the scheduler interface. It provides simple operations such as scheduling/unscheduling jobs, starting/stopping/pausing the scheduler. If you want to schedule your own software components to execute, they must implement the simple Job interface, which contains the method execute (). If you want to notify the component when the scheduled trigger time arrives, the component should implement the TriggerListener or JobListener interface. The main Quartz'processes' can be started and run in your own application or in a stand-alone application (using a remote interface).

2. Quartz. NET principal class and method parsing:

1. StdSchedulerFactory class: Create an QuartzScheduler instance.


  /// <summary>
    ///  Returns a handle to the scheduler generated by this factory. 
    /// </summary>
    /// <remarks>
    /// If <see cref = " Initialize () " /> Method 1 There is no previous call, and then the default ( no-arg ) <see cref = " Initialize () " /> Method will be called by this method. 
    /// </remarks>
    public virtual IScheduler GetScheduler()
    {
      if (cfg == null)
      {
        Initialize();
      }

      SchedulerRepository schedRep = SchedulerRepository.Instance;

      IScheduler sched = schedRep.Lookup(SchedulerName);

      if (sched != null)
      {
        if (sched.IsShutdown)
        {
          schedRep.Remove(SchedulerName);
        }
        else
        {
          return sched;
        }
      }

      sched = Instantiate();

      return sched;
    }

public interface ISchedulerFactory
  {
    /// <summary>
    /// Returns handles to all known Schedulers (made by any SchedulerFactory
    /// within this app domain.).
    /// </summary>
    ICollection<IScheduler> AllSchedulers { get; }

    /// <summary>
    /// Returns a client-usable handle to a <see cref="IScheduler" />.
    /// </summary>
    IScheduler GetScheduler();

    /// <summary>
    /// Returns a handle to the Scheduler with the given name, if it exists.
    /// </summary>
    IScheduler GetScheduler(string schedName);
  }

2. JobDetailImpl: Pass the details property for a given job instance.


/// <summary>
    ///  Gets or sets the same as the <see cref = " IJob " /> Associated <see cref = " JobDataMap " /> . 
    /// </summary>
    public virtual JobDataMap JobDataMap
    {
      get
      {
        if (jobDataMap == null)
        {
          jobDataMap = new JobDataMap();
        }
        return jobDataMap;
      }

      set { jobDataMap = value; }
    }

3. JobKey: The key consists of a name and a group. The name must be 1-only and within the group. If only 1 group is specified, the default group will use the name.


  [Serializable]
  public sealed class JobKey : Key<JobKey>
  {
    public JobKey(string name) : base(name, null)
    {
    }

    public JobKey(string name, string group) : base(name, group)
    {
    }

    public static JobKey Create(string name)
    {
      return new JobKey(name, null);
    }

    public static JobKey Create(string name, string group)
    {
      return new JobKey(name, group);
    }
  }

4.StdSchedulerFactory.Initialize():


/// <summary> 
    ///  Use initialization <see cref = " ISchedulerFactory " />
     /// The contents of the given key-value collection object. 
    /// </summary>
    public virtual void Initialize(NameValueCollection props)
    {
      cfg = new PropertiesParser(props);
      ValidateConfiguration();
    }

    protected virtual void ValidateConfiguration()
    {
      if (!cfg.GetBooleanProperty(PropertyCheckConfiguration, true))
      {
        // should not validate
        return;
      }

      // determine currently supported configuration keys via reflection
      List<string> supportedKeys = new List<string>();
      List<FieldInfo> fields = new List<FieldInfo>(GetType().GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy));
      // choose constant string fields
      fields = fields.FindAll(field => field.FieldType == typeof (string));

      // read value from each field
      foreach (FieldInfo field in fields)
      {
        string value = (string) field.GetValue(null);
        if (value != null && value.StartsWith(ConfigurationKeyPrefix) && value != ConfigurationKeyPrefix)
        {
          supportedKeys.Add(value);
        }
      }

      // now check against allowed
      foreach (string configurationKey in cfg.UnderlyingProperties.AllKeys)
      {
        if (!configurationKey.StartsWith(ConfigurationKeyPrefix) || configurationKey.StartsWith(ConfigurationKeyPrefixServer))
        {
          // don't bother if truly unknown property
          continue;
        }

        bool isMatch = false;
        foreach (string supportedKey in supportedKeys)
        {
          if (configurationKey.StartsWith(supportedKey, StringComparison.InvariantCulture))
          {
            isMatch = true;
            break;
          }
        }
        if (!isMatch)
        {
          throw new SchedulerConfigException("Unknown configuration property '" + configurationKey + "'");
        }
      }

    }

3. Quartz. Basic application of NET:

Here are some more general task processing codes:

1. Task processing help class:


 /// <summary>
  /// 任务处理帮助类
  /// </summary>
  public class QuartzHelper
  {
    public QuartzHelper() { }

    public QuartzHelper(string quartzServer, string quartzPort)
    {
      Server = quartzServer;
      Port = quartzPort;
    }

    /// <summary>
    /// 锁对象
    /// </summary>
    private static readonly object Obj = new object();

    /// <summary>
    /// 方案
    /// </summary>
    private const string Scheme = "tcp";

    /// <summary>
    /// 服务器的地址
    /// </summary>
    public static string Server { get; set; }

    /// <summary>
    /// 服务器的端口
    /// </summary>
    public static string Port { get; set; }

    /// <summary>
    /// 缓存任务所在程序集信息
    /// </summary>
    private static readonly Dictionary<string, Assembly> AssemblyDict = new Dictionary<string, Assembly>();

    /// <summary>
    /// 程序调度
    /// </summary>
    private static IScheduler _scheduler;

    /// <summary>
    /// 初始化任务调度对象
    /// </summary>
    public static void InitScheduler()
    {
      try
      {
        lock (Obj)
        {
          if (_scheduler != null) return;
          //配置文件的方式,配置quartz实例
          ISchedulerFactory schedulerFactory = new StdSchedulerFactory();
          _scheduler = schedulerFactory.GetScheduler();
        }
      }
      catch (Exception ex)
      {
        throw new Exception(ex.Message);
      }
    }

    /// <summary>
    /// 启用任务调度
    /// 启动调度时会把任务表中状态为“执行中”的任务加入到任务调度队列中
    /// </summary>
    public static void StartScheduler()
    {
      try
      {
        if (_scheduler.IsStarted) return;
        //添加全局监听
        _scheduler.ListenerManager.AddTriggerListener(new CustomTriggerListener(), GroupMatcher<TriggerKey>.AnyGroup());
        _scheduler.Start();

        //获取所有执行中的任务
        List<TaskModel> listTask = TaskHelper.GetAllTaskList().ToList();

        if (listTask.Count > 0)
        {
          foreach (TaskModel taskUtil in listTask)
          {
            try
            {
              ScheduleJob(taskUtil);
            }
            catch (Exception e)
            {
             throw new Exception(taskUtil.TaskName,e);
            }
          }
        }       
      }
      catch (Exception ex)
      {
        throw new Exception(ex.Message);
      }
    }

    /// <summary>
    /// 启用任务
    /// <param name="task">任务信息</param>
    /// <param name="isDeleteOldTask">是否删除原有任务</param>
    /// <returns>返回任务trigger</returns>
    /// </summary>
    public static void ScheduleJob(TaskModel task, bool isDeleteOldTask = false)
    {
      if (isDeleteOldTask)
      {
        //先删除现有已存在任务
        DeleteJob(task.TaskID.ToString());
      }
      //验证是否正确的Cron表达式
      if (ValidExpression(task.CronExpressionString))
      {
        IJobDetail job = new JobDetailImpl(task.TaskID.ToString(), GetClassInfo(task.AssemblyName, task.ClassName));
        //添加任务执行参数
        job.JobDataMap.Add("TaskParam", task.TaskParam);

        CronTriggerImpl trigger = new CronTriggerImpl
        {
          CronExpressionString = task.CronExpressionString,
          Name = task.TaskID.ToString(),
          Description = task.TaskName
        };
        _scheduler.ScheduleJob(job, trigger);
        if (task.Status == TaskStatus.STOP)
        {
          JobKey jk = new JobKey(task.TaskID.ToString());
          _scheduler.PauseJob(jk);
        }
        else
        {
          List<DateTime> list = GetNextFireTime(task.CronExpressionString, 5);
          foreach (var time in list)
          {
            LogHelper.WriteLog(time.ToString(CultureInfo.InvariantCulture));
          }
        }
      }
      else
      {
        throw new Exception(task.CronExpressionString + "不是正确的Cron表达式,无法启动该任务!");
      }
    }


    /// <summary>
    /// 初始化 远程Quartz服务器中的,各个Scheduler实例。
    /// 提供给远程管理端的后台,用户获取Scheduler实例的信息。
    /// </summary>
    public static void InitRemoteScheduler()
    {
      try
      {
        NameValueCollection properties = new NameValueCollection
        {
          ["quartz.scheduler.instanceName"] = "ExampleQuartzScheduler",
          ["quartz.scheduler.proxy"] = "true",
          ["quartz.scheduler.proxy.address"] =string.Format("{0}://{1}:{2}/QuartzScheduler", Scheme, Server, Port)
        };

        ISchedulerFactory sf = new StdSchedulerFactory(properties);

        _scheduler = sf.GetScheduler();
      }
      catch (Exception ex)
      {
        throw new Exception(ex.StackTrace);
      }
    }

    /// <summary>
    /// 删除现有任务
    /// </summary>
    /// <param name="jobKey"></param>
    public static void DeleteJob(string jobKey)
    {
      try
      {
        JobKey jk = new JobKey(jobKey);
        if (_scheduler.CheckExists(jk))
        {
          //任务已经存在则删除
          _scheduler.DeleteJob(jk);
          
        }
      }
      catch (Exception ex)
      {
        throw new Exception(ex.Message);
      }
    }

   

    /// <summary>
    /// 暂停任务
    /// </summary>
    /// <param name="jobKey"></param>
    public static void PauseJob(string jobKey)
    {
      try
      {
        JobKey jk = new JobKey(jobKey);
        if (_scheduler.CheckExists(jk))
        {
          //任务已经存在则暂停任务
          _scheduler.PauseJob(jk);
        }
      }
      catch (Exception ex)
      {
        throw new Exception(ex.Message);
      }
    }

    /// <summary>
    /// 恢复运行暂停的任务
    /// </summary>
    /// <param name="jobKey">任务key</param>
    public static void ResumeJob(string jobKey)
    {
      try
      {
        JobKey jk = new JobKey(jobKey);
        if (_scheduler.CheckExists(jk))
        {
          //任务已经存在则暂停任务
          _scheduler.ResumeJob(jk);
        }
      }
      catch (Exception ex)
      {
       throw new Exception(ex.Message);
      }
    }

    /// <summary> 
    /// 获取类的属性、方法 
    /// </summary> 
    /// <param name="assemblyName">程序集</param> 
    /// <param name="className">类名</param> 
    private static Type GetClassInfo(string assemblyName, string className)
    {
      try
      {
        assemblyName = FileHelper.GetAbsolutePath(assemblyName + ".dll");
        Assembly assembly = null;
        if (!AssemblyDict.TryGetValue(assemblyName, out assembly))
        {
          assembly = Assembly.LoadFrom(assemblyName);
          AssemblyDict[assemblyName] = assembly;
        }
        Type type = assembly.GetType(className, true, true);
        return type;
      }
      catch (Exception ex)
      {
        throw new Exception(ex.Message);
      }
    }

    /// <summary>
    /// 停止任务调度
    /// </summary>
    public static void StopSchedule()
    {
      try
      {
        //判断调度是否已经关闭
        if (!_scheduler.IsShutdown)
        {
          //等待任务运行完成
          _scheduler.Shutdown(true);
        }
      }
      catch (Exception ex)
      {
        throw new Exception(ex.Message);
      }
    }

    /// <summary>
    /// 校验字符串是否为正确的Cron表达式
    /// </summary>
    /// <param name="cronExpression">带校验表达式</param>
    /// <returns></returns>
    public static bool ValidExpression(string cronExpression)
    {
      return CronExpression.IsValidExpression(cronExpression);
    }

    /// <summary>
    /// 获取任务在未来周期内哪些时间会运行
    /// </summary>
    /// <param name="CronExpressionString">Cron表达式</param>
    /// <param name="numTimes">运行次数</param>
    /// <returns>运行时间段</returns>
    public static List<DateTime> GetNextFireTime(string CronExpressionString, int numTimes)
    {
      if (numTimes < 0)
      {
        throw new Exception("参数numTimes值大于等于0");
      }
      //时间表达式
      ITrigger trigger = TriggerBuilder.Create().WithCronSchedule(CronExpressionString).Build();
      IList<DateTimeOffset> dates = TriggerUtils.ComputeFireTimes(trigger as IOperableTrigger, null, numTimes);
      List<DateTime> list = new List<DateTime>();
      foreach (DateTimeOffset dtf in dates)
      {
        list.Add(TimeZoneInfo.ConvertTimeFromUtc(dtf.DateTime, TimeZoneInfo.Local));
      }
      return list;
    }


    public static object CurrentTaskList()
    {
      throw new NotImplementedException();
    }

    /// <summary>
    /// 获取当前执行的Task 对象
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    public static TaskModel GetTaskDetail(IJobExecutionContext context)
    {
      TaskModel task = new TaskModel();

      if (context != null)
      {

        task.TaskID = Guid.Parse(context.Trigger.Key.Name);
        task.TaskName = context.Trigger.Description;
        task.RecentRunTime = DateTime.Now;
        task.TaskParam = context.JobDetail.JobDataMap.Get("TaskParam") != null ? context.JobDetail.JobDataMap.Get("TaskParam").ToString() : "";
      }
      return task;
    }
  }

2. Set up tasks in execution:


public class TaskBll
  {
    private readonly TaskDAL _dal = new TaskDAL();

    /// <summary>
    ///  Get a list of tasks 
    /// </summary>
    /// <param name="pageIndex"></param>
    /// <param name="pageSize"></param>
    /// <returns></returns>
    public PageOf<TaskModel> GetTaskList(int pageIndex, int pageSize)
    {
      return _dal.GetTaskList(pageIndex, pageSize);
    }

    /// <summary>
    ///  Read all the tasks in the database 
    /// </summary>
    /// <returns></returns>
    public List<TaskModel> GetAllTaskList()
    {
      return _dal.GetAllTaskList();
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="taskId"></param>
    /// <returns></returns>
    public TaskModel GetById(string taskId)
    {
      throw new NotImplementedException();
    }

    /// <summary>
    ///  Delete a task 
    /// </summary>
    /// <param name="taskId"></param>
    /// <returns></returns>
    public bool DeleteById(string taskId)
    {
      return _dal.UpdateTaskStatus(taskId, -1);
    }

    /// <summary>
    ///  Modify task 
    /// </summary>
    /// <param name="taskId"></param>
    /// <param name="status"></param>
    /// <returns></returns>
    public bool UpdateTaskStatus(string taskId, int status)
    {
      return _dal.UpdateTaskStatus(taskId, status);
    }

    /// <summary>
    ///  Modify the next start time of the task 
    /// </summary>
    /// <param name="taskId"></param>
    /// <param name="nextFireTime"></param>
    /// <returns></returns>
    public bool UpdateNextFireTime(string taskId, DateTime nextFireTime)
    {
      return _dal.UpdateNextFireTime(taskId, nextFireTime);
    }

    /// <summary>
    ///  According to the task Id  Modify   Last run time 
    /// </summary>
    /// <param name="taskId"></param>
    /// <param name="recentRunTime"></param>
    /// <returns></returns>
    public bool UpdateRecentRunTime(string taskId, DateTime recentRunTime)
    {
      return _dal.UpdateRecentRunTime(taskId, recentRunTime);
    }

    /// <summary>
    ///  According to the task Id  Get Tasks 
    /// </summary>
    /// <param name="taskId"></param>
    /// <returns></returns>
    public TaskModel GetTaskById(string taskId)
    {
      return _dal.GetTaskById(taskId);
    }

    /// <summary>
    ///  Add Task 
    /// </summary>
    /// <param name="task"></param>
    /// <returns></returns>
    public bool Add(TaskModel task)
    {
      return _dal.Add(task);
    }

    /// <summary>
    ///  Modify task 
    /// </summary>
    /// <param name="task"></param>
    /// <returns></returns>
    public bool Edit(TaskModel task)
    {
      return _dal.Edit(task);
    }
  }

3. Mandate entity:


/// <summary>
  ///  Task entity 
  /// </summary>
  public class TaskModel
  {
    /// <summary>
    ///  Mission ID
    /// </summary>
    public Guid TaskID { get; set; }

    /// <summary>
    ///  Task name 
    /// </summary>
    public string TaskName { get; set; }

    /// <summary>
    ///  Task execution parameters 
    /// </summary>
    public string TaskParam { get; set; }

    /// <summary>
    ///  Operation frequency setting 
    /// </summary>
    public string CronExpressionString { get; set; }

    /// <summary>
    ///  Chinese description of mission operation frequency 
    /// </summary>
    public string CronRemark { get; set; }

    /// <summary>
    ///  Where the task lies DLL Corresponding assembly name 
    /// </summary>
    public string AssemblyName { get; set; }

    /// <summary>
    ///  Class of task 
    /// </summary>
    public string ClassName { get; set; }

    public TaskStatus Status { get; set; }

    /// <summary>
    ///  Task creation time 
    /// </summary>
    public DateTime? CreatedTime { get; set; }

    /// <summary>
    ///  Task modification time 
    /// </summary>
    public DateTime? ModifyTime { get; set; }

    /// <summary>
    ///  Last run time of task 
    /// </summary>
    public DateTime? RecentRunTime { get; set; }

    /// <summary>
    ///  Next run time of task 
    /// </summary>
    public DateTime? NextFireTime { get; set; }

    /// <summary>
    ///  Task notes 
    /// </summary>
    public string Remark { get; set; }

    /// <summary>
    ///  Delete or not 
    /// </summary>
    public int IsDelete { get; set; }
  }

4. Configuration file:


 # You can configure your scheduler in either <quartz> configuration section
# or in quartz properties file
# Configuration section has precedence

quartz.scheduler.instanceName = ExampleQuartzScheduler

# configure thread pool info
quartz.threadPool.type = Quartz.Simpl.SimpleThreadPool, Quartz
quartz.threadPool.threadCount = 10
quartz.threadPool.threadPriority = Normal

# job initialization plugin handles our xml reading, without it defaults are used
# quartz.plugin.xml.type = Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz
# quartz.plugin.xml.fileNames = ~/quartz_jobs.xml

# export this server to remoting context
quartz.scheduler.exporter.type = Quartz.Simpl.RemotingSchedulerExporter, Quartz
quartz.scheduler.exporter.port = 555
quartz.scheduler.exporter.bindName = QuartzScheduler
quartz.scheduler.exporter.channelType = tcp
quartz.scheduler.exporter.channelName = httpQuartz

4. Summary:

More in the project to use the timed task function, today's introduction of the components can be a good completion of a number of timed task requirements. This article is mainly used as an introduction to briefly introduce the background of components and how to use components. If you need to use them in the project, you can have a more in-depth understanding.


Related articles: