zoukankan      html  css  js  c++  java
  • 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 触发
    ");
            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 + "
    ");
        }
    }

    完整demo下载

  • 相关阅读:
    windows10、windows server 2016激活方法
    .NET 和 .NET Core 使用 JWT 授权验证
    vs(vs2017、vs2019)离线安装包下载、制作
    VS顶部增加签名描述信息
    .NET WebAPI 跨域问题(has been blocked by CORS policy:No AccessControlAllowOgigin header is present on the requested resource)
    .Net 和 .Net Core 集成Swagger 以及配合JWT身份验证
    jQuery实现公告无限循环滚动
    甩掉 ashx/asmx,使用jQuery.ajaxWebService请求WebMethod,Ajax处理更加简练(转)
    JS和JQUERY常见函数封装方式
    第07组 Beta冲刺 (1/5)(组长)
  • 原文地址:https://www.cnblogs.com/myron1024/p/13458806.html
Copyright © 2011-2022 走看看