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下载

 

posted @ 2020-08-08 18:07  码荣  阅读(3011)  评论(1编辑  收藏  举报