Quartz.net的快速简单上手使用以及防止IIS回收停止Job的处理
工作中偶尔会遇到需要定时处理的任务,最近工作上有个需求,要从一个第三方那边获取记录数据,然后解析数据文件入库。
第三方那边数据提供方式是FTP形式,存储的xml文件。所以打算用定时任务去处理,先写了个Demo试试手,在此记录一下。
1、创建项目后,引用Quartz.net包,我用的目前最新的3.1.0版本;还有log4net引用,配置好日志
2、创建Quartz定时任务的帮助类文件,下面这个是我网上找的,就直接拿来用了
public class QuartzUtil { private static ISchedulerFactory sf = null; private static IScheduler sched = null; static QuartzUtil() { } public static async void Init() { sf = new StdSchedulerFactory(); sched = await sf.GetScheduler(); await sched.Start(); } /// <summary> /// 添加Job 并且以定点的形式运行 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="JobName"></param> /// <param name="CronTime"></param> /// <param name="jobDataMap"></param> /// <returns></returns> public static async Task AddJob<T>(string JobName, string CronTime, Dictionary<string, object> map) where T : IJob { IJobDetail jobCheck = JobBuilder.Create<T>().WithIdentity(JobName, JobName + "_Group").Build(); if (map != null) { jobCheck.JobDataMap.PutAll(map); } ICronTrigger CronTrigger = new CronTriggerImpl(JobName + "_CronTrigger", JobName + "_TriggerGroup", CronTime); await sched.ScheduleJob(jobCheck, CronTrigger); } /// <summary> /// 添加Job 并且以定点的形式运行 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="JobName"></param> /// <param name="CronTime"></param> /// <returns></returns> public static async Task AddJob<T>(string JobName, string CronTime) where T : IJob { await AddJob<T>(JobName, CronTime, null); } /// <summary> /// 添加Job 并且以周期的形式运行 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="JobName"></param> /// <param name="StartTime"></param> /// <param name="EndTime"></param> /// <param name="SimpleTime">毫秒数</param> /// <returns></returns> public static async Task AddJob<T>(string JobName, DateTimeOffset StartTime, DateTimeOffset EndTime, int SimpleTime) where T : IJob { await AddJob<T>(JobName, StartTime, EndTime, TimeSpan.FromMilliseconds(SimpleTime)); } /// <summary> /// 添加Job 并且以周期的形式运行 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="JobName"></param> /// <param name="StartTime"></param> /// <param name="EndTime"></param> /// <param name="SimpleTime"></param> /// <returns></returns> public static async Task AddJob<T>(string JobName, DateTimeOffset StartTime, DateTimeOffset EndTime, TimeSpan SimpleTime) where T : IJob { await AddJob<T>(JobName, StartTime, EndTime, SimpleTime, new Dictionary<string, object>()); } /// <summary> /// 添加Job 并且以周期的形式运行 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="JobName"></param> /// <param name="StartTime"></param> /// <param name="EndTime"></param> /// <param name="SimpleTime">毫秒数</param> /// <param name="jobDataMap"></param> /// <returns></returns> public static async Task AddJob<T>(string JobName, DateTimeOffset StartTime, DateTimeOffset EndTime, int SimpleTime, string MapKey, object MapValue) where T : IJob { Dictionary<string, object> map = new Dictionary<string, object>(); map.Add(MapKey, MapValue); await AddJob<T>(JobName, StartTime, EndTime, TimeSpan.FromMilliseconds(SimpleTime), map); } /// <summary> /// 添加Job 并且以周期的形式运行 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="JobName"></param> /// <param name="StartTime"></param> /// <param name="EndTime"></param> /// <param name="SimpleTime"></param> /// <param name="jobDataMap"></param> /// <returns></returns> public static async Task AddJob<T>(string JobName, DateTimeOffset StartTime, DateTimeOffset EndTime, TimeSpan SimpleTime, Dictionary<string, object> map) where T : IJob { IJobDetail jobCheck = JobBuilder.Create<T>().WithIdentity(JobName, JobName + "_Group").Build(); jobCheck.JobDataMap.PutAll(map); ISimpleTrigger triggerCheck = new SimpleTriggerImpl(JobName + "_SimpleTrigger", JobName + "_TriggerGroup", StartTime, EndTime, SimpleTriggerImpl.RepeatIndefinitely, SimpleTime); await sched.ScheduleJob(jobCheck, triggerCheck); } /// <summary> /// 修改触发器时间,需要job名,以及修改结果 /// CronTriggerImpl类型触发器 /// </summary> public static async void UpdateTime(string jobName, string CronTime) { TriggerKey TKey = new TriggerKey(jobName + "_CronTrigger", jobName + "_TriggerGroup"); CronTriggerImpl cti = await sched.GetTrigger(TKey) as CronTriggerImpl; cti.CronExpression = new CronExpression(CronTime); await sched.RescheduleJob(TKey, cti); } /// <summary> /// 修改触发器时间,需要job名,以及修改结果 /// SimpleTriggerImpl类型触发器 /// </summary> /// <param name="jobName"></param> /// <param name="SimpleTime">分钟数</param> public static void UpdateTime(string jobName, int SimpleTime) { UpdateTime(jobName, TimeSpan.FromMinutes(SimpleTime)); } /// <summary> /// 修改触发器时间,需要job名,以及修改结果 /// SimpleTriggerImpl类型触发器 /// </summary> public static async void UpdateTime(string jobName, TimeSpan SimpleTime) { TriggerKey TKey = new TriggerKey(jobName + "_SimpleTrigger", jobName + "_TriggerGroup"); SimpleTriggerImpl sti = await sched.GetTrigger(TKey) as SimpleTriggerImpl; sti.RepeatInterval = SimpleTime; await sched.RescheduleJob(TKey, sti); } /// <summary> /// 暂停所有Job /// 暂停功能Quartz提供有很多,以后可扩充 /// </summary> public static void PauseAll() { sched.PauseAll(); } /// <summary> /// 恢复所有Job /// 恢复功能Quartz提供有很多,以后可扩充 /// </summary> public static void ResumeAll() { sched.ResumeAll(); } /// <summary> /// 删除Job /// 删除功能Quartz提供有很多,以后可扩充 /// </summary> /// <param name="JobName"></param> public static void DeleteJob(string JobName) { JobKey jk = new JobKey(JobName, JobName + "_Group"); sched.DeleteJob(jk); } /// <summary> /// 卸载定时器 /// </summary> /// <param name="waitForJobsToComplete">是否等待job执行完成</param> public static void Shutdown(bool waitForJobsToComplete) { if (sched != null) { sched.Shutdown(waitForJobsToComplete); } } /// <summary> /// 判断任务是否已经建立 /// </summary> /// <param name="jobName">任务名</param> public static async Task<bool> CheckExist(string jobName) { bool isExists = false; TriggerKey triggerKey = new TriggerKey(jobName + "_CronTrigger", jobName + "_TriggerGroup"); isExists = await sched.CheckExists(triggerKey); return isExists; } /// <summary> /// 判断简单任务是否已经建立 /// </summary> /// <param name="jobName">任务名</param> public static async Task<bool> CheckSimpleExist(string jobName) { bool isExists = false; TriggerKey triggerKey = new TriggerKey(jobName + "_SimpleTrigger", jobName + "_TriggerGroup"); isExists = await sched.CheckExists(triggerKey); return isExists; }
}
3、创建一个Job类,继承Quartz的IJob接口,实现Execute方法。
public class GetRecordJob : IJob { public async Task Execute(IJobExecutionContext context) { Log.Info(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); } }
4、Job类和Quartz的帮助类都建好之后,接下来就是应用了
QuartzUtil.AddJob<GetRecordJob>("job1", "0/10 * * * * ?"); //cron表达式,10s执行一次
如果你想在程序启动的时候就开始执行定时任务,就把上面这句代码加到Application_Start里就行。不过记得要先初始化一下Quartz哦。
protected void Application_Start() { //应用程序启动时加载log4net设置 XmlConfigurator.Configure(); AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); // 初始化启动定时任务引擎 QuartzUtil.Init(); // 启动设定的任务 QuartzUtil.AddJob<GetRecordJob>("job1", "0/10 * * * * ?"); //10s执行一次 }
至此一个可以正常运行的简单的小定时任务Demo就写好了。但是在我发布到IIS上挂着跑准备挂一两天看有没有问题的时候,问题就来了。发布到IIS后,Application_Start事件需要有请求才会触发。这倒问题不大,部署完之后 点一下就好了。
问题是,看日志发现定时任务跑了一阵子后就停掉不跑了,看日志的时间,从开始记录到最后一条日志的时间间隔是20分钟左右,想起来IIS的闲置超时时间默认就是20分钟,应该是IIS回收了导致定时任务停止,经过测试也确定了是这个原因。接下来就得解决这个问题。
1)设置IIS闲置超时时间
设置IIS应用程序池的闲置超时时间为0,
固定回收时间间隔(默认1740分钟)设置为0。
不过,即使可以将IIS进程池回收关掉,仍然不建议设置为完全不回收,如果长时间不回收,可能会存在内存溢出的问题。
具体看业务场景吧,我这需求对数据实时性要求不高,偶尔停下来歇息一下也不是坏事。
2)代码处理
我们知道关闭、重启网站或者IIS程序池是会执行Application_End事件的,Application_End 事件后 就跟刚部署到IIS的站点一样,在发送第一个针对该 Web 应用程序的 Http 请求后,IIS 才会自动启动 Web 应用程序,
既然IIS是因为程序闲置没有收到请求而回收进程的,那就在Application_End 事件里再提交一个请求给该 Web 应用程序,从而“激活”关闭的应用程序不就可以了?
“激活”程序之后,定时任务就又可以继续自动执行啦。
public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { //应用程序启动时加载log4net设置 XmlConfigurator.Configure(); Log.Info("Application_Start 触发"); AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); // 初始化启动定时任务引擎 QuartzUtil.Init(); // 启动设定的任务 QuartzUtil.AddJob<GetRecordJob>("job1", "0/10 * * * * ?"); // 10s执行一次 Log.Info("Application_Start 启动设定的任务"); } protected void Application_End(object sender, EventArgs e) { Log.Info("Application_End 触发\r\n"); Activate(); } public static void Restart() { Log.Info("Restart() 卸载所有定时器... "); QuartzUtil.Shutdown(false); //true 等待当前job执行完再关闭,false 直接关闭 Log.Info("Restart() 准备重启 "); HttpRuntime.UnloadAppDomain(); //会触发Application_End 事件 Log.Info("Restart() 重启完成 "); } /// <summary> /// “激活”程序 /// IIS回收后,将触发Application_End事件,需发起一次请求才会触发 Application_Start事件开始执行定时任务 /// </summary> public static void Activate() { string host = System.Configuration.ConfigurationManager.AppSettings["WebUrl"].ToString(); string url = host.TrimEnd('/') + "/Home/Ping"; Log.Info("PING URL : " + url); string res = HttpUtils.HttpGet(url); Log.Info("PING RESULT:" + res + "\r\n"); } }