0%

虽然Quartz.NET被园子里的大神们写烂了,自己还是整理了一篇,结尾会附上源码地址。

Quartz.NET是一款功能齐全的开源作业调度框架,小至的应用程序,大到企业系统都可以适用。Quartz是作者James House用JAVA语言编写的,而Quartz.NET是从Quartz移植过来的C#版本。

在一般企业,可以利用Quartz.Net框架做各种的定时任务,例如,数据迁移、跑报表等等。

另外还有一款Hangfire https://www.hangfire.io/,也是作业调度框架,有自带监控web后台,比Quartz.Net更加易用,简单。但是Cron最低只支持到分钟级。然而Hangfire不是今天的主角,有机会再介绍。

新建一个控制台项目,通过Nuget管理下载Quartz包

using System; using System.Collections.Specialized; using Quartz; using Quartz.Impl; namespace QuartzDotNetDemo
{ class Program
{ static void Main(string[] args)
{ //创建一个调度器工厂
var props = new NameValueCollection
{
{ “quartz.scheduler.instanceName”, “QuartzDotNetDemo” }
}; var factory = new StdSchedulerFactory(props); //获取调度器
var sched = factory.GetScheduler();
sched.Start(); //定义一个任务,关联”HelloJob”
var job = JobBuilder.Create()
.WithIdentity(“myJob”, “group1”)
.Build(); //由触发器每40秒触发执行一次任务
var trigger = TriggerBuilder.Create()
.WithIdentity(“myTrigger”, “group1”)
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInSeconds(40)
.RepeatForever())
.Build();

         sched.ScheduleJob(job, trigger);
    }
} public class HelloJob : IJob
{ public void Execute(IJobExecutionContext context)
    {
        Console.WriteLine("你好");
    }
}

}

View Code

一个简单的调度任务流程如下:

有几个重要类和概念需要了解一下:

  • IScheduler - 与调度器交互的主要API.
  • IJob -由执行任务实现的接口。
  • IJobDetail - 定义Job实例
  • ITrigger - 按照定义的时间让任务执行的组件.
  • JobBuilder - 用于定义或者创建JobDetai
  • TriggerBuilder -用于定义或生成触发器实例

他们之间的关系大概如下:

当有空闲线程同时,到了该执行的时间,那么就会由Trigger去触发绑定的Job执行它的Excute方法,假如这次没执行完,却到了下一次的运行时间,如果有空闲线程就仍然会再次执行。但是如果没有空闲线程,会等到腾出空闲的线程才会执行,但是超过quartz.jobStore.misfireThreshold设置的时间就会放弃这次的运行。

当然也可以在Job贴上DisallowConcurrentExecution标签让Job进行单线程跑,避免没跑完时的重复执行。

在第一个简单的demo里是无法良好的在实际中使用,因此我们需要改造一下。

需要的第三方包:

  • Autofac version=”4.6.2”
  • Autofac.Extras.Quartz version=”3.4.0”
  • Common.Logging version=”3.4.1”
  • Common.Logging.Core version=”3.4.1”
  • Common.Logging.Log4Net1213 version=”3.4.1”
  • log4net version=”2.0.3”
  • Newtonsoft.Json version=”10.0.3”
  • Quartz version=”2.6.1”
  • Topshelf version=”4.0.3”
  • Topshelf.Autofac version=”3.1.1”
  • Topshelf.Log4Net version=”3.2.0”
  • Topshelf.Quartz version=”0.4.0.1”

Topshelf

Topshelf是一款为了方便安装部署在Windows系统下而诞生的宿主框架,它基于控制台项目,为开发人员带来更方便的调试和部署。

官网:https://topshelf.readthedocs.io/en/latest/index.html

那我们可以在Program.cs里写入以下代码:

using Topshelf; using Topshelf.Autofac; namespace QuartzDotNetDemo
{ class Program
{ static void Main(string[] args)
{
HostFactory.Run(config => {
config.SetServiceName(JobService.ServiceName);
config.SetDescription(“Quartz.NET的demo”);
config.UseLog4Net();
config.UseAutofacContainer(JobService.Container);

            config.Service<JobService>(setting => {
                JobService.InitSchedule(setting);
                setting.ConstructUsingAutofacContainer();
                setting.WhenStarted(o \=> o.Start());
                setting.WhenStopped(o \=> o.Stop());
            });
        });
    }
}

}

View Code

JobService

此类用来读取配置信息、初始化调度任务和注入ioc容器

public class JobService
{ #region 初始化
private static readonly ILog Log = LogManager.GetLogger(typeof(JobService)); private const string JobFile = “JobsConfig.xml”; private static readonly string JobNamespceFormat; public static readonly string ServiceName; private static readonly Jobdetail[] JobList; public static IContainer Container; static JobService()
{ var job = JobFile.XmlToObject();

        ServiceName \= job.Quartz.ServiceName;
        JobNamespceFormat \= job.Quartz.Namespace;
        JobList \= job.Quartz.JobList.JobDetail;

        Log.Info("Jobs.xml 初始化完毕");

        InitContainer();
    } #endregion

    /// <summary>
    /// 初始化调度任务 /// </summary>
    /// <param name="svc"></param>
    public static void InitSchedule(ServiceConfigurator<JobService> svc)
    {
        svc.UsingQuartzJobFactory(Container.Resolve<IJobFactory>); foreach (var job in JobList)
        {
            svc.ScheduleQuartzJob(q \=> {
                q.WithJob(JobBuilder.Create(Type.GetType(string.Format(JobNamespceFormat, job.JobName)))
                    .WithIdentity(job.JobName, ServiceName)
                    .Build);

                q.AddTrigger(() \=> TriggerBuilder.Create()
                    .WithCronSchedule(job.Cron)
                    .Build());

                Log.InfoFormat("任务 {0} 已完成调度设置", string.Format(JobNamespceFormat, job.JobName));
            });
        }

        Log.Info("调度任务 初始化完毕");
    } /// <summary>
    /// 初始化容器 /// </summary>
    private static void InitContainer()
    { var builder = new ContainerBuilder();
        builder.RegisterModule(new QuartzAutofacFactoryModule());
        builder.RegisterModule(new QuartzAutofacJobsModule(typeof(JobService).Assembly));
        builder.RegisterType<JobService>().AsSelf(); var execDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); var files = Directory.GetFiles(execDir, "QuartzDotNetDemo.\*.dll", SearchOption.TopDirectoryOnly); if (files.Length > 0)
        { var assemblies = new Assembly\[files.Length\]; for (var i = 0; i < files.Length; i++)
                assemblies\[i\] \= Assembly.LoadFile(files\[i\]);

            builder.RegisterAssemblyTypes(assemblies)
                .Where(t \=> t.GetInterfaces().ToList().Contains(typeof(IService)))
                .AsSelf()
                .InstancePerLifetimeScope();
        }

        Container \= builder.Build();
        Log.Info("IOC容器 初始化完毕");
    } public bool Start()
    {
        Log.Info("服务已启动"); return true;
    } public bool Stop()
    {
        Container.Dispose();
        Log.Info("服务已关闭"); return false;
    }
}

View Code

触发器类型

一共有4种:

  • WithCalendarIntervalSchedule
  • WithCronSchedule
  • WithDailyTimeIntervalSchedule
  • WithSimpleSchedule

在项目中使用的是WithCronSchedule,因为cron表达式更加灵活、方便。

Cron表达式

字段名

是否必填

值范围

特殊字符

Seconds

YES

0-59

, - * /

Minutes

YES

0-59

, - * /

Hours

YES

0-23

, - * /

Day of month

YES

1-31

, - * ? / L W

Month

YES

1-12 or JAN-DEC

, - * /

Day of week

YES

1-7 or SUN-SAT

, - * ? / L #

Year

NO

empty, 1970-2099

, - * /

例子:

 "0 0/5 * * * ?"    —- 每5分钟触发一次

  "10 0/5 * * * ?``"   —–每5分钟触发一次,每分钟10秒(例如:10:00:10 am,10:05:10,等等)

  "0 0/30 8-9 5,20 * ?" ----在每个月的第5到20个小时之间,每隔半小时就会触发一个触发点。请注意,触发器不会在上午10点触发,仅在8点,8点30分,9点和9点30分

BaseJob

我们定义一个BaseJob写入公共处理逻辑,例如:业务逻辑禁用、公共异常日志消息推送等等。再由具体的Job去继承重写基类的ExecuteJob,简单的适配器模式运用。

public abstract class BaseJob : IJob
{ protected readonly CommonService CommonService; protected BaseJob(CommonService commonService)
{
CommonService = commonService;
} public void Execute(IJobExecutionContext context)
{ //公共逻辑
CommonService.Enabled(); //job逻辑
ExecuteJob(context);
} public abstract void ExecuteJob(IJobExecutionContext context);
}

View Code

最后按照惯例双手奉上demo源码。https://github.com/SkyChenSky/QuartzDotNetDemo.git

如果错误麻烦在下面评论指出,我会及时修改。