ASP.NET Core 2.2+Quartz.Net 实现Web定时任务

作者:Julian_酱

链接:http://www.cnblogs.com/mi12205599/p/10361763.html


作为一枚后端程序狗,项目实践常遇到定时任务的工作,最容易想到的的思路就是利用Windows计划任务/wndows service程序/Crontab程序等主机方法在主机上部署定时任务程序/脚本。


但是很多时候,若使用的是共享主机或者受控主机,这些主机不允许你私自安装exe程序、Windows服务程序。


码甲会想到在web程序中做定时任务, 目前有两个方向:


1、AspNetCore自带的HostService, 这是一个轻量级的后台服务, 需要搭配timer完成定时任务


2、老牌Quartz.Net组件,支持复杂灵活的Scheduling、支持ADO/RAM Job任务存储、支持集群、支持监听、支持插件。


此处我们的项目使用稍复杂的Quartz.net实现Web定时任务。

项目背景

最近需要做一个计数程序:采用redis计数,设定每小时将当日累积数据持久化到关系型数据库sqlite。


添加Quartz.Net Nuget 依赖包:<PackageReference Include="Quartz" Version="3.0.6" />


1、定义定时任务内容: Job

2、设置触发条件: Trigger

3、将Quartz.Net集成进AspNet Core

头脑风暴

IScheduler类包装了上述背景需要完成的第①②点工作 ,SimpleJobFactory定义了生成指定的Job任务的过程,这个行为是利用反射机制调用无参构造函数构造出的Job实例。


下面是源码:


//----------------选自Quartz.Simpl.SimpleJobFactory类-----------
using System;
using Quartz.Logging;
using Quartz.Spi;
using Quartz.Util;
namespace Quartz.Simpl
{
   /// <summary>
   /// The default JobFactory used by Quartz - simply calls
   /// <see cref="ObjectUtils.InstantiateType{T}" /> on the job class.
   /// </summary>
   /// <seealso cref="IJobFactory" />
   /// <seealso cref="PropertySettingJobFactory" />
   /// <author>James House</author>
   /// <author>Marko Lahma (.NET)</author>
   public class SimpleJobFactory : IJobFactory
   {
       private static readonly ILog log = LogProvider.GetLogger(typeof (SimpleJobFactory));
       /// <summary>
       /// Called by the scheduler at the time of the trigger firing, in order to
       /// produce a <see cref="IJob" /> instance on which to call Execute.
       /// </summary>
       /// <remarks>
       /// It should be extremely rare for this method to throw an exception -
       /// basically only the case where there is no way at all to instantiate
       /// and prepare the Job for execution.  When the exception is thrown, the
       /// Scheduler will move all triggers associated with the Job into the
       /// <see cref="TriggerState.Error" /> state, which will require human
       /// intervention (e.g. an application restart after fixing whatever
       /// configuration problem led to the issue with instantiating the Job).
       /// </remarks>
       /// <param name="bundle">The TriggerFiredBundle from which the <see cref="IJobDetail" />
       ///   and other info relating to the trigger firing can be obtained.</param>
       /// <param name="scheduler"></param>
       /// <returns>the newly instantiated Job</returns>
       /// <throws>  SchedulerException if there is a problem instantiating the Job. </throws>
       public virtual IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
       
{
           IJobDetail jobDetail = bundle.JobDetail;
           Type jobType = jobDetail.JobType;
           try
           {
               if (log.IsDebugEnabled())
               {
                   log.Debug($"Producing instance of Job '{jobDetail.Key}', class={jobType.FullName}");
               }
               return ObjectUtils.InstantiateType<IJob>(jobType);
           }
           catch (Exception e)
           {
               SchedulerException se = new SchedulerException($"Problem instantiating class '{jobDetail.JobType.FullName}'", e);
               throw se;
           }
       }

       /// <summary>
       /// Allows the job factory to destroy/cleanup the job if needed.
       /// No-op when using SimpleJobFactory.
       /// </summary>
       public virtual void ReturnJob(IJob job)
       
{
           var disposable = job as IDisposable;
           disposable?.Dispose();
       }
   }
}


//------------------节选自Quartz.Util.ObjectUtils类------------
public static T InstantiateType<T>(Type type)
{
    if(type == null)
    {
         throw new ArgumentNullException(nameof(type), "Cannot instantiate null");
    }
    ConstructorInfo ci = type.GetConstructor(Type.EmptyTypes);
    if (ci == null)
    {
         throw new ArgumentException("Cannot instantiate type which has no empty constructor", type.Name);
    }
    return (T) ci.Invoke(new object[0]);
}


很多时候,定义的Job任务依赖了其他组件(Job实例化时多参),此时默认的SimpleJobFactory不能满足实例化要求, 需要考虑将Job任务作为依赖注入组件,加入依赖注入容器。


关键思路:


① IScheduler 开放了JobFactory 属性,便于你控制Job任务的实例化方式;


JobFactories may be of use to those wishing to have their application produce IJob instances via some special mechanism, such as to give the opportunity for dependency injection


②AspNet Core的服务架构是以依赖注入为基础的,利用ASPNET Core已有的依赖注入容器IServiceProvider管理Job任务的创建过程。

编码实践

1、定义Job内容

// -------每小时将redis数据持久化到sqlite, 每日凌晨跳针,持久化昨天全天数据-
public class UsageCounterSyncJob : IJob
{
       private readonly EqidDbContext _context;
       private readonly IDatabase _redisDB1;
       private readonly ILogger _logger;
       public UsageCounterSyncJob(EqidDbContext context, RedisDatabase redisCache, ILoggerFactory loggerFactory)
       
{
           _context = context;
           _redisDB1 = redisCache[1];
           _logger = loggerFactory.CreateLogger<UsageCounterSyncJob>();
       }
       public async Task Execute(IJobExecutionContext context)
       
{
           // 触发时间在凌晨,则同步昨天的计数
           var _day = DateTime.Now.ToString("yyyyMMdd");
           if (context.FireTimeUtc.LocalDateTime.Hour == 0)
               _day = DateTime.Now.AddDays(-1).ToString("yyyyMMdd");

           await SyncRedisCounter(_day);
           _logger.LogInformation("[UsageCounterSyncJob] Schedule job executed.");

       }
       ......
}


2、注册Job和Trigger

namespace EqidManager
{
   using IOCContainer = IServiceProvider;
   // Quartz.Net启动后注册job和trigger
   public class QuartzStartup
   {
       public IScheduler _scheduler { get; set; }
       private readonly ILogger _logger;
       private readonly IJobFactory iocJobfactory;
       public QuartzStartup(IOCContainer IocContainer, ILoggerFactory loggerFactory)
       
{
           _logger = loggerFactory.CreateLogger<QuartzStartup>();
           iocJobfactory = new IOCJobFactory(IocContainer);
           var schedulerFactory = new StdSchedulerFactory();
           _scheduler = schedulerFactory.GetScheduler().Result;
           _scheduler.JobFactory = iocJobfactory;
       }
       public void Start()
       
{
           _logger.LogInformation("Schedule job load as application start.");
           _scheduler.Start().Wait();
           var UsageCounterSyncJob = JobBuilder.Create<UsageCounterSyncJob>()
              .WithIdentity("UsageCounterSyncJob")
              .Build();
           var UsageCounterSyncJobTrigger = TriggerBuilder.Create()
               .WithIdentity("UsageCounterSyncCron")
               .StartNow()
               // 每隔一小时同步一次
               .WithCronSchedule("0 0 * * * ?")      // Seconds,Minutes,Hours,Day-of-Month,Month,Day-of-Week,Year(optional field)
               .Build();
           _scheduler.ScheduleJob(UsageCounterSyncJob, UsageCounterSyncJobTrigger).Wait();
           _scheduler.TriggerJob(new JobKey("UsageCounterSyncJob"));
       }
       public void Stop()
       
{
           if (_scheduler == null)
           {
               return;
           }
           if (_scheduler.Shutdown(waitForJobsToComplete: true).Wait(30000))
               _scheduler = null;
           else
           {
           }
           _logger.LogCritical("Schedule job upload as application stopped");
       }
   }
   /// <summary>
   /// IOCJobFactory :实现在Timer触发的时候注入生成对应的Job组件
   /// </summary>
   public class IOCJobFactory : IJobFactory
   {
       protected readonly IOCContainer Container;
       public IOCJobFactory(IOCContainer container)
       
{
           Container = container;
       }
       //Called by the scheduler at the time of the trigger firing, in order to produce
       //a Quartz.IJob instance on which to call Execute.
       public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
       
{
           return Container.GetService(bundle.JobDetail.JobType) as IJob;
       }
       // Allows the job factory to destroy/cleanup the job if needed.
       public void ReturnJob(IJob job)
       
{
       }
   }
}


3、结合ASpNet Core 注入组件;绑定Quartz.Net

//-------------------------------截取自Startup文件---------------
......
services.AddTransient<UsageCounterSyncJob>();    
// 这里使用瞬时依赖注入
services.AddSingleton<QuartzStartup>();
......
// 绑定Quartz.Net
public void Configure(IApplicationBuilder app, Microsoft.AspNetCore.Hosting.IApplicationLifetime lifetime, ILoggerFactory loggerFactory)
{
    var quartz = app.ApplicationServices.GetRequiredService<QuartzStartup>();
    lifetime.ApplicationStarted.Register(quartz.Start);
    lifetime.ApplicationStopped.Register(quartz.Stop);
}


附:IIS 网站低频访问导致工作进程进入闲置状态的 解决办法


IIS为网站默认设定了20min闲置超时时间:20分钟内没有处理请求、也没有收到新的请求,工作进程就进入闲置状态。


IIS上低频web访问会造成工作进程关闭,此时应用程序池回收,Timer等线程资源会被销毁;当工作进程重新运作,Timer可能会重新生成起效, 但我们的设定的定时Job可能没有按需正确执行。


640?wx_fmt=png


故为在IIS网站实现低频web访问下的定时任务:


设置Idle TimeOut =0;同时将【应用程序池】->【正在回收】->不勾选【回收条件】      


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/272165.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RocketMQ核心概念

生产者Producer和消费者Consumer NameServer作用 Broker和Topic

交叉编译、软硬链接

什么是交叉编译&#xff1f;交叉编译是一个行为&#xff0c;是在一个平台上生成另一个平台上的可执行代码。 本地编译&#xff1a;本地编译可以理解为&#xff0c;在当前编译平台下&#xff0c;编译出来的程序只能放到当前平台下运行。平时我们常见的软件开发&#xff0c;都是…

Linus下安装maven

下载maven安装包 wget http://mirror.bit.edu.cn/apache/maven/binaries/apache-maven-3.2.2-bin.tar.gz 解压 tar -zxvf apache-maven-3.2.2-bin.tar.gz 配置maven环境变量 查看maven解压后安装包目录 vi /etc/profile 进入最底部&#xff0c;按insert,添加环境变量&#x…

linux内核开发基础(linux内核源码、树莓派源码编译、SD卡挂载)

首先下载树莓派linux内核源码&#xff1a; 下载网址&#xff1a;https://github.com/raspberrypi/linux在树莓派使用指令&#xff1a;uname -r查看当前树莓派的版本号&#xff0c;然后选择对应的linux内核版本号进行下载。 将linux内核源码从共享文件夹拷贝到SYSTEM文件夹&am…

Linux实时查看进程命令top笔记

top命令是Linux下常用的性能分析工具&#xff0c;能够实时显示Linux系统中各个进程的资源占用状况&#xff0c;类似于Windows系统的任务管理器功能。 top命令的语法格式&#xff1a; top [-] [d] [p] [q] [c] [C] [S] [s] [n] 常用参数说明 d 指定每两次屏幕信息刷新之间的时间…

文件系统(文件系统目录结构、磁盘分区、虚拟文件系统)、linux内核结构框图

什么是文件系统&#xff1f; 常规认知就是根目录下那些文件&#xff0c;但其实并不是那样。文件系统是操作系统用于明确存储设备&#xff08;常见的是磁盘&#xff0c;也有基于NAND Flash的固态硬盘&#xff09;或分区上的文件的方法和数据结构&#xff1b;即在存储设备上组织…

Linux进程终止命令kill或kill all​笔记

在linux命令下&#xff0c;如果需要终止某个进程&#xff0c;可以使用kill或者killall等命令来实现。终止命令的原理都是向linux内核发送一个系统操作的信号以及某个进程的ID&#xff0c;然后系统内核会根据指定的进程ID进行相应的处理。 kill命令典型的用法&#xff1a;首先使…

linux驱动(驱动编译、字符设备驱动框架、交叉编译树莓派驱动、树莓派驱动本地编译)

什么是驱动&#xff1a; 驱动就是对底层硬件设备的操作进行封装&#xff0c;并向上层提供函数接口。 设备分类&#xff1a; linux系统将设备分为3类&#xff1a;字符设备、块设备、网络设备。 字符设备&#xff1a;指只能一个字节一个字节读写的设备&#xff0c;不能随机读取…

docker启动报错  (iptables failed: iptables --wait -t nat -A DOCKER -p tcp -d 0/0 --dport 9876 -j DNAT --

docker启动报错 : (iptables failed: iptables --wait -t nat -A DOCKER -p tcp -d 0/0 --dport 9876 -j DNAT --to-destination 172.17.0.2:9876 ! -i docker0: iptables: No chain/target/match by that name. 解决方案&#xff1a; systemctl restart docker

最详细的docker安装rocketMQ教程来了

RocketMQ是一款分布式、队列模型的消息中间件&#xff0c;是由阿里巴巴设计的&#xff0c;具有以下特点&#xff1a; 支持严格的消息顺序 支持Topic与Queue两种模式 亿级消息堆积能力 比较友好的分布式特性 同时支持Push与Pull方式消费消息 历经多次天猫双十一海量消息考验…

树莓派IO口驱动代码的编写、微机总线地址、物理地址、虚拟地址、BCM2835芯片手册

地址总线&#xff1a; 百度百科解释&#xff1a; 地址总线 (Address Bus&#xff1b;又称&#xff1a;位址总线) 属于一种电脑总线 &#xff08;一部份&#xff09;&#xff0c;是由CPU 或有DMA 能力的单元&#xff0c;用来沟通这些单元想要存取&#xff08;读取/写入&#xff…

夺命雷公狗---DEDECMS----26dedecms面包屑导航的实现

我们在很多项目里面都会用到面包屑导航&#xff0c;而dedecms里面也是给我们封装好面包屑导航的了,如下图所示&#xff1a; 在dede里面实现面包屑导航主要用到{dede:field.position/}标签&#xff0c;我们首先来修改下article_movie.htm内容页的模版文件&#xff1a; 我们修改成…

rust油桶用什么打_草莓用什么膨大素好?草莓膨大剂什么时间打?草莓用什么肥料膨大...

农资365公众号&#xff0c;了解更多生根、根腐、重茬、土传、枯黄萎、根烂病、防治根结线虫、微生物菌肥、膨大坐果、抗病增产的防治方法&#xff01;草莓含有丰富的营养&#xff0c;并且种植效益较高&#xff0c;其种植范围也比较广。草莓种植期间有很多因素影响草莓果实膨大&…

docker安装kafka,超级简单的

简介 kafka是一个分布式消息队列。具有高性能、持久化、多副本备份、横向扩展能力。生产者往队列里写消息&#xff0c;消费者从队列里取消息进行业务逻辑。一般在架构设计中起到解耦、削峰、异步处理的作用。 kafka对外使用topic的概念&#xff0c;生产者往topic里写消息&…

Linux中常见的环境变量笔记

1、变量&#xff1a;BASHBash Shell的全路径比如&#xff1a;echo $BASH2、变量&#xff1a;BASH_VERSIONBash Shell的版本号3、变量&#xff1a;EUID记录当前用户的UID。root用户值为0。4、FUNCNAME在用户函数体内部&#xff0c;记录当前函数体的函数名。5、变量&#xff1a;H…

消防信号二总线有没电压_春晓161#地块人防工程消防电源监控系统的设计与应用...

涂志燕安科瑞电气股份有限公司&#xff0c;上海 嘉定 201801&#xff1b;摘要&#xff1a;本文简述了消防设备电源的组成原理&#xff0c;分析了消防设备电源监控系统在应用中的设计依据和相关规范。通过安科瑞消防设备电源监控系统在春晓161#地块项目的实例介绍&#xff0c;阐…

大学慕课数据结构单元测试——华中科技大学

第一章绪论单元测试 一、单选(2分) 1、​___C__ 是数据的最小单位。 A.信息项 B.数据元素 C.数据项 D.表元素 2、​以下说法不正确的是 ___B___。 A.数据元素是数据的基本单位 B.数据项可由若干个数据元素构成 C.数据可由若干个数据元素构成 D.数据项是不可分割的最小…

RocketMQ同步刷盘和异步刷盘

刷盘机制 同步刷盘和异步刷盘 在broker配置文件里修改参数配置是同步还是异步

vim模式下报错E37: No write since last change No write since last change for buffer “ “

报错如下图所示&#xff1a; 网上的解决方法&#xff1a; 文件为只读文件&#xff0c;无法修改。使用命令:w!强制存盘即可在vim模式下&#xff0c;键入以下命令&#xff1a;:w&#xff01;存盘后在使用vim命令检查是否保存&#xff0c;如未保存&#xff0c;编辑后重复以上操作…

Linux中Shell中取消变量和特殊变量的笔记

1、取消变量取消变量也就是将变量从内存中释放出去&#xff0c;可以使用unset 后面加变量名即可&#xff0c;当然函数的释放同样可以采用该方式处理。比如&#xff1a;name"123"echo ${name}输出&#xff1a;123unset nameecho ${name}输出&#xff1a;#取消函数示例…