gpt4 book ai didi

AspNet Core上实现web定时任务实例

转载 作者:qq735679552 更新时间:2022-09-29 22:32:09 27 4
gpt4 key购买 nike

CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.

这篇CFSDN的博客文章AspNet Core上实现web定时任务实例由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.

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

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

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

  • ①.AspNetCore自带的HostService, 这是一个轻量级的后台服务, 需要搭配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实例。下面是源码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
//----------------选自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内容:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// -------每小时将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:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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 。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
//-------------------------------截取自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可能没有按需正确执行.

AspNet Core上实现web定时任务实例

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

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

最后此篇关于AspNet Core上实现web定时任务实例的文章就讲到这里了,如果你想了解更多关于AspNet Core上实现web定时任务实例的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com