一、Quartz.NET简介
1.1、.NET Framework自带的Timer定时器介绍
很多的软件项目中都会使用到定时任务、定时轮询数据库同步,定时邮件通知等功能。.NET Framework具有“内置”定时器功能,通过System.Timers.Timer类实现。
自带定时器的缺点:
①在使用Timer类需要面对的问题:计时器没有持久化机制;
②计时器具有不灵活的计划(仅能设置开始时间和重复间隔,没有基于日期,时间等);
③计时器不使用线程池(每个定时器一个线程);计时器没有真正的管理方案 - 你必须编写自己的机制,以便能够记住,组织和检索任务的名称等。
1.2、Quartz.NET介绍
Quartz.NET是一个强大、开源、轻量的作业调度框架,可以与任何其他软件系统集成或一起使用。作业调度程序是一个系统,负责在执行预处理程序时执行(或通知)其他软件组件 - 确定(调度)时间到达。Quartz是非常灵活的,并且包含多个使用范例,可以单独使用或一起使用,以实现您所需的行为,并使您能够以您的项目看起来最“自然”的方式编写代码。
Quartz.NET优点:
①组件的使用非常轻便,并且需要非常少的设置/配置,如果您的需求相对基础,它实际上可以使用“开箱即用。
②是容错的,并且可以在系统重新启动之间保留(记住)您的预定作业。
③能够用它来为执行一个作业而创建简单的或复杂的作业调度。它有很多特征,如:数据库支持,集群,插件,支持cron-like表达式等等。非常适合在平时的工作中,定时轮询数据库同步,定时邮件通知,定时处理数据等。
Quartz是作为一个小的动态链接库(.dll文件)分发的,它包含所有的核心Quartz功能。 此功能的主要接口(API)是调度程序接口。 它提供简单的操作,如调度/非调度作业,启动/停止/暂停调度程序。如果你想安排你自己的软件组件执行,他们必须实现简单的Job接口,它包含方法execute()。 如果希望在计划的触发时间到达时通知组件,则组件应实现TriggerListener或JobListener接口。主要的Quartz'进程'可以在您自己的应用程序或独立应用程序(使用远程接口)中启动和运行。
1.3、Quarzt.NET的官方地址
官网:http://www.quartz-scheduler.net/
源码:https://github.com/quartznet/quartznet
示例:Quartz.NET Quick Start Guide | Quartz.NET (quartz-scheduler.net)
Quartz.NET是 OpenSymphony 的 Quartz API 的.NET移植,用C#改写,可用于winform和Web应用中。它灵活而不复杂,你能够用它来为执行一个作业而创建简单的或复杂的作业调度。Quartz.NET 3.0 已经开始支持 .NET Core/.NET Standard 2.0。
Job 为作业的接口,JobDetail 用来描述 Job 的实现类及其它相关的静态信息;Trigger 作为作业的定时管理工具。
一个 Trigger 只能对应一个作业实例,而一个作业实例可对应多个 Trigger ;
Scheduler 做为定时任务容器,它包含了所有触发器和作业,每个 Scheduler 都存有 JobDetail 和 Trigger的注册,一个 Scheduler 中可以注册多个 JobDetail 和多个 Trigger 。
二、实现步骤
2.1、安装Quartz.NET包
2.2、创建一个简单的定时任务示例
这里实现一个定时执行方法输出执行时间的示例
1、编写需要定时执行的方法,必须继承【IJob】
/***
* Title:"数据采集" 项目
* 主题:定时任务
* Description:
* 功能:XXX
* Date:2021
* Version:0.1版本
* Author:Coffee
* Modify Recoder:
*/
using Quartz;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace TestQuartz.FixedTimeTask
{
class TimeJob : IJob
{
/// <summary>
/// 作业调度定时执行的方法
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public async Task Execute(IJobExecutionContext context)
{
string str = $"{this.GetType().Name} 方法执行,执行时间是:{DateTime.Now} ID:{context.FireInstanceId}" +Environment.NewLine;
await Console.Out.WriteLineAsync(str);
}
}//Class_end
}
2、要定时执行这个方法,你需要先用【IJobDetail】接口绑定该方法,然后再使用【ITrriger】接口监听,接着使用【StdSchedulerFactory】调度工厂实例化【IScheduler】调度器接口启动;
//1、使用IJobDetail 接口绑定需要定时执行的方法【TimeJob】
IJobDetail job = JobBuilder.Create<TimeJob>()
.WithIdentity("job1", "group1")
.Build();
// 2、使用ITrriger监听器每10秒重复监听
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("trigger1", "group1")
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInSeconds(10)
.RepeatForever())
.Build();
// 3、通知调度器用定义好的监听器执行该绑定的方法
await scheduler.ScheduleJob(job, trigger);
// 注意:你也可以让调度器使用多个监听器监听同一个方法
// await scheduler.ScheduleJob(job, new List<ITrigger>() { trigger1, trigger2 }, replace: true);
3、确定任务执行的时间间隔
//任务执行的时间间隔(可自定义,比如这里的是10秒)
await Task.Delay(TimeSpan.FromSeconds(10));
4、还可以添加日志记录,不过当没有任何日志接口对接时,该日志则不进行记录。
//日志类需要继承ILogProvider接口
private class ConsoleLogProvider : ILogProvider
{
public Logger GetLogger(string name)
{
return (level, func, exception, parameters) =>
{
if (level >= LogLevel.Info && func != null)
{
Console.WriteLine("[" + DateTime.Now.ToLongTimeString() + "] [" + level + "] " + func(), parameters);
}
return true;
};
}
public IDisposable OpenNestedContext(string message)
{
throw new NotImplementedException();
}
public IDisposable OpenMappedContext(string key, object value, bool destructure = false)
{
throw new NotImplementedException();
}
}
//调用方法
LogProvider.SetCurrentLogProvider(new ConsoleLogProvider());
2.3、完整的调用示例
using System;
using System.Threading.Tasks;
using Quartz;
using Quartz.Impl;
using Quartz.Logging;
namespace QuartzSampleApp
{
public class Program
{
private static async Task Main(string[] args)
{
LogProvider.SetCurrentLogProvider(new ConsoleLogProvider());
// Grab the Scheduler instance from the Factory
StdSchedulerFactory factory = new StdSchedulerFactory();
IScheduler scheduler = await factory.GetScheduler();
// and start it off
await scheduler.Start();
// define the job and tie it to our HelloJob class
IJobDetail job = JobBuilder.Create<HelloJob>()
.WithIdentity("job1", "group1")
.Build();
// Trigger the job to run now, and then repeat every 10 seconds
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("trigger1", "group1")
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInSeconds(10)
.RepeatForever())
.Build();
// Tell quartz to schedule the job using our trigger
await scheduler.ScheduleJob(job, trigger);
// some sleep to show what's happening
await Task.Delay(TimeSpan.FromSeconds(60));
// and last shut down the scheduler when you are ready to close your program
await scheduler.Shutdown();
Console.WriteLine("Press any key to close the application");
Console.ReadKey();
}
// simple log provider to get something to the console
private class ConsoleLogProvider : ILogProvider
{
public Logger GetLogger(string name)
{
return (level, func, exception, parameters) =>
{
if (level >= LogLevel.Info && func != null)
{
Console.WriteLine("[" + DateTime.Now.ToLongTimeString() + "] [" + level + "] " + func(), parameters);
}
return true;
};
}
public IDisposable OpenNestedContext(string message)
{
throw new NotImplementedException();
}
public IDisposable OpenMappedContext(string key, object value, bool destructure = false)
{
throw new NotImplementedException();
}
}
}
public class TimeJob: IJob
{
public async Task Execute(IJobExecutionContext context)
{
string str = $"{this.GetType().Name} 方法执行,执行时间是:{DateTime.Now} ID:{context.FireInstanceId}" +Environment.NewLine;
await Console.Out.WriteLineAsync(str);
}
}
}
三、封装好了一个简易的单个任务调度器
3.1、单个定时任务调度器的核心
/***
* Title:"数据采集" 项目
* 主题:单个任务调度器
* Description:
* 功能:
* 1、启动定时方法
* 2、关闭定时方法
* Date:2021
* Version:0.1版本
* Author:Coffee
* Modify Recoder:
*/
using Quartz;
using Quartz.Impl;
using Quartz.Logging;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace FixedTimeTask
{
class SingleTaskScheduler
{
#region 基础参数
//调度工厂
private static StdSchedulerFactory _factory;
//调度器
private static IScheduler _scheduler;
//详细任务
private IJobDetail _job;
//触发器
private ITrigger _trigger;
//触发的时间间隔
private int _trrigerInterval = 5;
//任务执行的时间间隔
private int _taskRunInterval = 20;
//执行次数
private uint _runNumber = 0;
#endregion
/// <summary>
/// 构造函数
/// </summary>
/// <param name="taskRunInterval">任务执行的时间间隔</param>
/// <param name="trrigerInterval">触发器的时间间隔</param>
public SingleTaskScheduler(int taskRunInterval,int trrigerInterval)
{
_taskRunInterval = taskRunInterval;
_trrigerInterval = trrigerInterval;
}
#region 公有方法
/// <summary>
/// 启动定时方法
/// </summary>
/// <returns></returns>
public async Task StartFiexedTimeMethod<T>()where T:IJob
{
Console.WriteLine("开始调用定时任务!!!");
//创建日志
LogProvider.SetCurrentLogProvider(new ConsoleLogProvider());
//从调度工厂获取调度器实例
_factory = new StdSchedulerFactory();
_scheduler = await _factory.GetScheduler();
//启动实例
await _scheduler.Start();
//绑定定时执行方法
BindFiexedTimeMethod<T>();
//创建定时执行的触发器
CreateFiexedTimeTrigger(_trrigerInterval);
//告诉quartz用我们的触发器安排任务
await _scheduler.ScheduleJob(_job,_trigger);
//任务执行的时间间隔
await Task.Delay(TimeSpan.FromSeconds(_taskRunInterval));
Console.WriteLine("------------执行完成一轮定时任务----------");
}
/// <summary>
/// 关闭定时方法
/// </summary>
/// <returns>返回关闭结果</returns>
public bool CloseFiexedTimeMethod()
{
Task task=_scheduler?.Shutdown();
return task.IsCompletedSuccessfully;
}
#endregion
#region 私有方法
/// <summary>
/// 绑定定时方法
/// </summary>
private void BindFiexedTimeMethod<T>()where T:IJob
{
_job = JobBuilder.Create<T>()
//.WithIdentity("时间任务1", "组1")
.Build();
}
/// <summary>
/// 创建定时执行触发器
/// </summary>
/// <param name="seconds"></param>
private void CreateFiexedTimeTrigger(int seconds)
{
if (seconds < 0) return;
_trigger = TriggerBuilder.Create()
//.WithIdentity("时间任务触发器1", "组1")
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInSeconds(seconds)
.RepeatForever())
.Build();
}
#endregion
}//Class_end
}
3.2、单定时任务调度器使用方法
//1-引用命名空间
using FixedTimeTask;
//2-实例化单定时任务调度器(可以*定义:任务执行的时间间隔、与触发器执行的时间间隔)
SingleTaskScheduler taskScheduler = new SingleTaskScheduler(10,5);
//3-启动定时方法
await taskScheduler.StartFiexedTimeMethod<TimeJob>();
//4-最后可以关闭定时方法
bool success = taskScheduler.CloseFiexedTimeMethod();