<sup id="sjju3"><noscript id="sjju3"></noscript></sup>
    <big id="sjju3"></big>

  • <blockquote id="sjju3"></blockquote>
    <blockquote id="sjju3"></blockquote>

      <td id="sjju3"></td>

      <big id="sjju3"></big>
        <code id="sjju3"><strong id="sjju3"><dl id="sjju3"></dl></strong></code>
      1. AspNet Core上实现web定时任务实例

         更新时间:2019年02月11日 15:49:01   投稿:laozhang   我要评论

        在本篇文章里小编给大家分享了关于AspNet Core上实现web定时任务的实例内容,有兴趣的朋友们学习参考下。

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

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

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

        • ①.AspNetCore?#28304;?#30340;HostService, 这是一个轻?#32771;?#30340;后台服务, 需要搭配timer完成定时任务
        • ②.老牌Quartz.Net组件,支持复杂灵活的Scheduling、支持ADO/RAM Job任务存储、支持集群、支持监听、支持插件。

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

        项目背景

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

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

        • ①.定义定时任务内容: Job
        • ②.设置触发条件: Trigger
        • ③.将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任务依赖了其他组件,这时默认的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 服务的创建过程。

        编码实践

        ① 定义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.");
          }
          ......
         }

        ②注册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)
          {
          }
         }
        }

        ③结合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 网站低频访?#23454;?#33268;工作进程进入闲置状态的 解决办法

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

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

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

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

        相关文章

        最新评论

        2018白小姐一肖中特马
        <sup id="sjju3"><noscript id="sjju3"></noscript></sup>
        <big id="sjju3"></big>

      2. <blockquote id="sjju3"></blockquote>
        <blockquote id="sjju3"></blockquote>

          <td id="sjju3"></td>

          <big id="sjju3"></big>
            <code id="sjju3"><strong id="sjju3"><dl id="sjju3"></dl></strong></code>
          1. <sup id="sjju3"><noscript id="sjju3"></noscript></sup>
            <big id="sjju3"></big>

          2. <blockquote id="sjju3"></blockquote>
            <blockquote id="sjju3"></blockquote>

              <td id="sjju3"></td>

              <big id="sjju3"></big>
                <code id="sjju3"><strong id="sjju3"><dl id="sjju3"></dl></strong></code>