net core WebApi——定时任务Quartz
目录
前言
本来打算昨天都开始写这篇,就因为要把小团队的博客整理汇总,一看二哈的博客那么多,一个个复制粘贴肯定麻烦(其实是我自己觉得复制麻烦),所以穿插着写了个小爬虫,后续写差不多了就拿出来晾晾吧(py菜鸡水平)。
之前开发的时候,忽略了记录,等到想写点儿啥跟后台有关的东西的时候,还得一点点回忆,最近是因为同事给我说,"哎,每个月把数据给我统计下做个界面展示啊"。一想到每个月我要做次操作就头疼,咦,不对,这不就是写个定时任务就搞定了嘛。
quartz
其实在选这个定时器的类库的时候,我在hangfire两者间徘徊,后来是想到不管用什么方法什么工具都是次要的,主要看你怎么用,用到哪,图形界面是需要但不是必要,分秒级别的控制也都是看你自己业务需要,定时器就后台挂起运行就行了没必要让我看见,想操作了再说吧,就这样愉快的决定使用quartz。
首先,依然是在我们util的工程引入包。
引入完成后,在我们的入口startup中添加实例的注册声明。
public iserviceprovider configureservices(iservicecollection services) { serviceinjection.configurerepository(services); services.addmvc().setcompatibilityversion(compatibilityversion.version_2_2); //任务调度 services.tryaddsingleton<ischedulerfactory, stdschedulerfactory>(); }
schedulerfactory任务调度就好比一个公司的老大,trigger就是一个项管,job就是苦逼的码农,老大想要一天搞个app,就跟项管说一句,我要一天后要东西,这时候项管心里就有数了,一天后的那个时间,找到码农,直接剥夺他的代码执行,好了app出来了,苦逼的结束并不意味着真的结束,这老大一看可以啊,好了,以后每天我要一个成品app,如此循环往复,项管不厌其烦,码农换了又换(当然job不会)。
项管还会有多个,每个项管下面可不止一个码农。
像这样的情况可能有些夸张,但是类似的情况却真实存在。
ok,完了之后,我们来创建一个myjob。
public class myjob : ijob { public task execute(ijobexecutioncontext context) { return task.run(() => { logutil.debug("执行myjob"); }); } }
之后我们来写个简单的quartzutil。
public class quartzutil { private static ischedulerfactory _schedulerfactory; private static ischeduler _scheduler; /// <summary> /// 添加任务 /// </summary> /// <param name="type">类</param> /// <param name="jobkey">键</param> /// <param name="trigger">触发器</param> public static async void add(type type, jobkey jobkey, itrigger trigger = null) { init(); _scheduler = await _schedulerfactory.getscheduler(); await _scheduler.start(); if (trigger == null) { trigger = triggerbuilder.create() .withidentity("april.trigger") .withdescription("default") .withsimpleschedule(x=>x.withmisfirehandlinginstructionfirenow().withrepeatcount(-1)) .build(); } var job = jobbuilder.create(type) .withidentity(jobkey) .build(); await _scheduler.schedulejob(job, trigger); } /// <summary> /// 恢复任务 /// </summary> /// <param name="jobkey">键</param> public static async void resume(jobkey jobkey) { init(); _scheduler = await _schedulerfactory.getscheduler(); logutil.debug($"恢复任务{jobkey.group},{jobkey.name}"); await _scheduler.resumejob(jobkey); } /// <summary> /// 停止任务 /// </summary> /// <param name="jobkey">键</param> public static async void stop(jobkey jobkey) { init(); _scheduler = await _schedulerfactory.getscheduler(); logutil.debug($"暂停任务{jobkey.group},{jobkey.name}"); await _scheduler.pausejob(jobkey); } /// <summary> /// 初始化 /// </summary> private static void init() { if (_schedulerfactory == null) { _schedulerfactory = aprilconfig.serviceprovider.getservice<ischedulerfactory>(); } } }
触发器的使用,有很多种方式,可以使用简单的执行一次/多久执行一次/循环执行几次等等。
还有可以使用cron表达式:
简单来说,corn从左到右(用空格隔开):秒 分 小时 月份中的日期 月份 星期中的日期 年份,举个例子,就像开头说的,让我每隔一个月执行一次统计,写法就是 0 0 0 1 * ?,当然这就有涉及到什么符号的问题了,这种不需要强记,需要的时候查下就行,推荐一个工具站吧,cron校验工具。
测试
感觉我的博客内容好单调,内容框架就是开头,代码,测试,结尾,唉
不过做啥东西,测试少不了,最起码你的东西能用,才说明可行。
我们在values添加一个方法,这里我们5s一执行(懒得等)。
[httpget] [route("quartztest")] public void quartztest(int type) { jobkey jobkey = new jobkey("demo","group1"); switch (type) { //添加任务 case 1: var trigger = triggerbuilder.create() .withdescription("触发器描述") .withidentity("test") //.withschedule(cronschedulebuilder.cronschedule("0 0/30 * * * ? *").withmisfirehandlinginstructiondonothing()) .withsimpleschedule(x=>x.withintervalinseconds(5).repeatforever().withmisfirehandlinginstructionignoremisfires()) .build(); quartzutil.add(typeof(myjob), jobkey, trigger); break; //暂停任务 case 2: quartzutil.stop(jobkey); break; //恢复任务 case 3: quartzutil.resume(jobkey); break; } }
让我们来愉快的运行吧,记得appsettings配置个路径访问白名单。
一番1,2,3输入完之后,我们来看下日志。
- 执行任务--- ok
- 暂停任务--- ok
- 恢复任务--- ok
问题及解决方法
但是问题出现了,暂停恢复后,连执行了多次(具体看你间隔时间以及你的频率),这个是有点儿怪异,当时我记得这个问题让我鼓捣了好半天,也是各种查资料查方法,但实际呢这个是quartz的保护机制,为了防止你的操作是因为不可预知的问题导致的,所以有个重做错过的任务,另外我们的代码中触发器也有这个配置withmisfirehandlinginstructionignoremisfires。
我们来去掉这个重做机制并测试。
crontrigger
规则 | 介绍 |
---|---|
withmisfirehandlinginstructiondonothing | 不触发立即执行; 等待下次cron触发频率到达时刻开始按照cron频率依次执行 |
withmisfirehandlinginstructionignoremisfires | 以错过的第一 个频率时间立刻开始执行; 重做错过的所有频率周期后; 当下一次触发频率发生时间大于当前时间后,再按照正常的cron频率依次执行 |
withmisfirehandlinginstructionfireandproceed | 以当前时间为触发频率立刻触发一次执行; 然后按照cron频率依次执行 |
simpletrigger
规则 | 介绍 |
---|---|
withmisfirehandlinginstructionfirenow | 以当前时间为触发频率立即触发执行; 执行至finaltime的剩余周期次数;以调度或恢复调度的时刻为基准的周期频率,finaltime根据剩余次数和当前时间计算得到; 调整后的finaltime会略大于根据starttime计算的到的finaltime值 |
withmisfirehandlinginstructionignoremisfires | 以错过的第一个频率时间立刻开始执行; 重做错过的所有频率周期;当下一次触发频率发生时间大于当前时间以后,按照interval的依次执行剩下的频率; 共执行repeatcount+1次 |
withmisfirehandlinginstructionnextwithexistingcount | 不触发立即执行; 等待下次触发频率周期时刻,执行至finaltime的剩余周期次数; 以starttime为基准计算周期频率,并得到finaltime; 即使中间出现pause,resume以后保持finaltime时间不变 |
withmisfirehandlinginstructionnowwithexistingcount | 以当前时间为触发频率立即触发执行; 执行至finaltime的剩余周期次数; 以调度或恢复调度的时刻为基准的周期频率,finaltime根据剩余次数和当前时间计算得到; 调整后的finaltime会略大于根据starttime计算的到的finaltime值 |
withmisfirehandlinginstructionnextwithremainingcount | 不触发立即执行; 等待下次触发频率周期时刻,执行至finaltime的剩余周期次数; 以starttime为基准计算周期频率,并得到finaltime; 即使中间出现pause,resume以后保持finaltime时间不变 |
withmisfirehandlinginstructionnowwithremainingcount | 以当前时间为触发频率立即触发执行; 执行至finaltime的剩余周期次数; 以调度或恢复调度的时刻为基准的周期频率,finaltime根据剩余次数和当前时间计算得到; 调整后的finaltime会略大于根据starttime计算的到的finaltime值 |
配置规则介绍参考:
之前在net framework遇到过一个问题,iis回收问题,网站在20分钟无请求后就停了,任务也紧跟着停了,当时的解决方法是做个windows服务来定时请求网站保持活跃,当然也可以通过禁止回收来保持网站一直运行。
net core中还没部署运行,如果有相关问题,后续也会补充上来一起交流解决。
小结
定时任务在一个后台系统中一般使用场景还算广泛,主要是sql数据统计,sql/文件备份,定时推送等,具体问题具体分析,net core 3.0都已经问世了,学无止境啊。
下一篇: 快速整理代码(c#)
推荐阅读
-
asp.net core 2.0 webapi集成signalr(实例讲解)
-
基于Quartz定时调度任务(详解)
-
SpringBoot与Quartz集成实现分布式定时任务集群的代码实例
-
Spring整合Quartz定时任务并在集群、分布式系统中的应用
-
.NET CORE webapi epplus 导入导出 (实习第一个月的笔记)
-
spring boot整合quartz实现多个定时任务的方法
-
ASP.NET Core MVC/WebApi基础系列1
-
ASP.NET Core MVC/WebApi基础系列2
-
Asp.Net Core WebAPI使用Swagger时API隐藏和分组详解
-
详解.net core webapi 前后端开发分离后的配置和部署