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");
    }
}

 

完整demo下載

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM