前言

本来打算昨天都开始写这篇,就因为要把小团队的博客整理汇总,一看二哈的博客那么多,一个个复制粘贴肯定麻烦(其实是我自己觉得复制麻烦),所以穿插着写了个小爬虫,后续写差不多了就拿出来晾晾吧(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 Task 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 Task 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 Task 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>();
}
}
}

感谢jiulang指点,异步编程避免使用async void,因为这样会导致异常无法捕获。

触发器的使用,有很多种方式,可以使用简单的执行一次/多久执行一次/循环执行几次等等。

还有可以使用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值

配置规则介绍参考:https://blog.csdn.net/yangshangwei/article/details/78539433

之前在net framework遇到过一个问题,IIS回收问题,网站在20分钟无请求后就停了,任务也紧跟着停了,当时的解决方法是做个windows服务来定时请求网站保持活跃,当然也可以通过禁止回收来保持网站一直运行。

net core中还没部署运行,如果有相关问题,后续也会补充上来一起交流解决。

小结

定时任务在一个后台系统中一般使用场景还算广泛,主要是sql数据统计,sql/文件备份,定时推送等,具体问题具体分析,net core 3.0都已经问世了,学无止境啊。

net core WebApi——定时任务Quartz的更多相关文章

  1. .Net Core 3.1浏览器后端服务(五) 引入定时任务Quartz.Net

    一.前言 近期项目中遇到一些需求,需要定时写入数据库,定时刷新缓存的问题,因此需要引入任务调度机制. 我的选择是使用 Quartz.Net,使用的版本是 3.2.4 这里强调一点:3.x的版本与2.x ...

  2. spring学习总结(mybatis,事务,测试JUnit4,日志log4j&slf4j,定时任务quartz&spring-task,jetty,Restful-jersey等)

    在实战中学习,模仿博客园的部分功能.包括用户的注册,登陆:发表新随笔,阅读随笔:发表评论,以及定时任务等.Entity层设计3张表,分别为user表(用户),essay表(随笔)以及comment表( ...

  3. .Net Core WebAPI 基于Task的同步&异步编程快速入门

    .Net Core WebAPI 基于Task的同步&异步编程快速入门 Task.Result async & await 总结 并行任务(Task)以及基于Task的异步编程(asy ...

  4. asp.net core webapi之跨域(Cors)访问

    这里说的跨域是指通过js在不同的域之间进行数据传输或通信,比如用ajax向一个不同的域请求数据,或者通过js获取页面中不同域的框架中(iframe)的数据.只要协议.域名.端口有任何一个不同,都被当作 ...

  5. ASP.NET Core WebAPI 开发-新建WebAPI项目

    ASP.NET Core WebAPI 开发-新建WebAPI项目, ASP.NET Core 1.0 RC2 即将发布,我们现在来学习一下 ASP.NET Core WebAPI开发. 网上已经有泄 ...

  6. Asp.net Core WebApi 使用Swagger做帮助文档,并且自定义Swagger的UI

    WebApi写好之后,在线帮助文档以及能够在线调试的工具是专业化的表现,而Swagger毫无疑问是做Docs的最佳工具,自动生成每个Controller的接口说明,自动将参数解析成json,并且能够在 ...

  7. AngularJS 2调用.net core WebAPI的几个坑

    前几天,按照AngularJS2的英雄指南教程走了一遍,教程网址是http://origin.angular.live/docs/ts/latest/tutorial/. 在步骤完成后,又更进一步,在 ...

  8. Asp.Net Core WebApi学习笔记(四)-- Middleware

    Asp.Net Core WebApi学习笔记(四)-- Middleware 本文记录了Asp.Net管道模型和Asp.Net Core的Middleware模型的对比,并在上一篇的基础上增加Mid ...

  9. Net Core WebAPI

    Net Core WebAPI .Net Core WebAPI 基于Task的同步&异步编程快速入门 Task.Result async & await 总结 并行任务(Task)以 ...

随机推荐

  1. Spring源码剖析1:初探Spring IOC核心流程

    本文大致地介绍了IOC容器的初始化过程,只列出了比较重要的过程和代码,可以从中看出IOC容器执行的大致流程. 接下来的文章会更加深入剖析Bean容器如何解析xml,注册和初始化bean,以及如何获取b ...

  2. 利用SSH端口转发实现远程访问内网主机远程桌面(一) 建立SSH转发

    近期家里更换了移动的宽带,拨号后拿到的是10开头的内网IP,就不能像之前一样通过路由器的端口映射实现从外网访问主机的远程桌面.这种情况下可以利用一台具有公网IP的服务器充当中转,利用SSH的隧道转发功 ...

  3. jar在linux上运行

    打jar后一直在linux远程的运行: nohup java -jar xxx.jar & CRT(打开时运行):(另外上传文件可使用“rz”命令,上传jar包) java -jar xxx. ...

  4. mac下面有epoll?

    没有的,但是mac下面有kqueue,跟epoll原理是差不多的. 这个是没办法的,如果实在需要,就用Ubuntu吧,这个也可以无缝迁移. 更多资源,更多文章由小白技术社提供(是我啦)

  5. centos7yum安装mysql5.7

    https://www.digitalocean.com/community/tutorials/how-to-install-mysql-on-centos-7 https://typecodes. ...

  6. HibernateSynchronizer的安装与使用

    HibernateSynchronizer的作用是自动生成hibernate配置文件,即hibernate.cfg.xml文件,映射文件,Plain Object类文件和一些基础数据库操作文件. 安装 ...

  7. URAL-1627-Join 生成树计数

    传送门:https://vjudge.net/problem/URAL-1627 题意: 给定一个n*m的图,问图中“.”的点生成的最小生成树有多少个. 思路: 生成树的计数,需要用Kirchhoff ...

  8. 【欧拉降幂】Super_log

    In Complexity theory, some functions are nearly O(1)O(1), but it is greater then O(1)O(1). For examp ...

  9. Spring boot 自定义 Resolver 支持 interface 类型参数

    在编写 RestController 层的代码时,由于数据实体类定义了接口及实现类,本着面向接口编程的原则,我使用了接口作为 RestController 方法的入参. 代码大致如下(省略具体业务部分 ...

  10. 调用arcpy包批量进行矢量掩膜提取

    使用一个polygon矢量提取某个文件夹中所有的tif格式栅格数据 (要确保先安装好arcpy包) import arcpy arcpy.CheckOutExtension("spatial ...