工作中偶爾會遇到需要定時處理的任務,最近工作上有個需求,要從一個第三方那邊獲取記錄數據,然后解析數據文件入庫。
第三方那邊數據提供方式是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"); } }