9

C#编写一个在asp.net core 3.1下的简单的corn模式的计划任务和一个更简单的定时器类

 3 years ago
source link: http://www.cnblogs.com/kugar/p/14210057.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

asp.net core 下,新增了一个BackgroundService用来实现能在后台跑一个长久运行的任务,因此,也可以用来替换掉原来使用的static的Timer组件,

Timer组件主要有以下几个麻烦的地方

1.如果是需要长时间跑的定时任务,需要定义为static,,在asp.net core下,无法利用到DI,无法从DI中获取DbContext之类的

2.启动定时器的时候,需要在start.cs自己手动启动

3.Timer是传入处理函数的方式,如果有好几个定时器,拼在一起,代码会看起来比较乱

4.使用Timer也无法实现corn表达式

5.Timer中也无法使用async异步处理

首先,我们来实现一个简单的定时器功能,用来替换掉Timer类:

1.TimerHostedService 定时器的基类

 1 /// <summary>
 2     /// 用于在后台执行一个定时任务,用于取代TimeEx,在asp.net core的环境下使用,继承该类后,使用 services.AddHostedService<当前类类型>();后,自动在后台启动当前定时任务
 3     /// </summary>
 4     public abstract class TimerHostedService : BackgroundService
 5     {
 6         private IServiceProvider _provider = null;
 7 
 8         protected TimerHostedService(IServiceProvider provider)
 9         {
10             _provider = provider;
11         }
12 
13         protected override async Task ExecuteAsync(CancellationToken stoppingToken)
14         {
15             if (Enabled && Internal>0)  //如果启动服务的时候,Enabled为false或者不设置间隔时间,则该定时器永久不启动
16             {
17                 while (!stoppingToken.IsCancellationRequested)  //如果站点触发停止,则会使用stoppingToken的IsCancellactionRequest判断是否由IIS之类的停止应用
18                 {
19                     await Task.Delay(Internal, stoppingToken);
20 
21                     if (!stoppingToken.IsCancellationRequested)
22                     {
23                         using (var scope = _provider.CreateScope())
24                         {
25                             try
26                             {
27                                 await Run(scope.ServiceProvider, stoppingToken);
28                             }
29                             catch (Exception e)
30                             {
31 
32                             }
33                         }
34                     }
35 
36                     
37                 }
38             }
39 
40             
41 
42             return;
43         }
44 
45         /// <summary>
46         /// 实际执行的定时器处理函数
47         /// </summary>
48         /// <param name="serviceScope">当次的Ioc容器,可获取当前程序中用于注入的容器内的类</param>
49         /// <param name="stoppingToken">是否暂停</param>
50         /// <returns></returns>
51         protected abstract Task Run(IServiceProvider serviceProvider, CancellationToken stoppingToken);
52 
53         /// <summary>
54         /// 定时器间隔触发时间,单位是ms
55         /// </summary>
56         protected abstract int Internal {  get; }
57 
58         /// <summary>
59         /// 当前定时器是否启用,true为定时器有效,false为停用
60         /// </summary>
61         public virtual bool Enabled { set; get; } = true;
62     }

2.实现一个定时器:

 1 /// <summary>
 2     /// 检查订单是否完成的后台任务
 3     /// </summary>
 4     public class CheckOrderCompleteTask:TimerHostedService
 5     {
 6         public CheckOrderCompleteTask(IServiceProvider provider) : base(provider)
 7         {
 8             this.Enabled = CustomConfigManager.Default["Timer:CheckBlessCompleted"].ToBool();
 9         }
10 
11         protected override async Task Run(IServiceProvider serviceProvider, CancellationToken stoppingToken)
12         {
13             var s = (XXXService) serviceProvider.GetService(typeof(XXXService)); //可以从DI容器中获取service或者dbcontext
17             await s.CheckXXX(stoppingToken);  //此处为实际执行的定时任务处理函数
18         }
19 
20         protected override int Internal { get; } = 1000 * 60 * 5;
21     }

在Start.cs中,注册该任务:

1 public void ConfigureServices(IServiceCollection services)
2 {
3     //.....其他代码
4     services.AddHostedService<CheckOrderCompleteTask>(); //此处将服务注册后,即由asp.net core在启动后,自动启动该服务
5 }

这样,就会有asp.net core自动启动该任务

由于上面定义的是一个定时器,有时候需要比如半夜12点,或者中午12点运行,这种场景下,就需要使用到计划任务的组件了,,.net下,常用的,一般有hangfire跟Quartz.Net,,这两个组件功能比较完善,而且也带有管理功能,but,..就是有时候复杂了点,通常有些不复杂的计划任务,比如又不想直接引入那么复杂的组件,那么可以根据上面的定时组件,变化出一个简单的计划任务组件:

    /// <summary>
    /// 一个简单的corn模式的计划任务<br/>用于在一些已知的计划时间执行某些任务的情况下使用,Cron属性在服务启动后,变无法修改,如需配置运行时可修改,请使用Hangfire之类的其他第三方框架
    /// </summary>
    public abstract class SimpleScheduledTaskService : BackgroundService
    {
        private IServiceProvider _provider = null;
        private CrontabSchedule _crontab = null;
        private string _cron;
        private bool _enabled=true;
        private bool _isInited = false;

        protected SimpleScheduledTaskService(IServiceProvider provider)
        {
            _provider = provider;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            try
            {
                _crontab = CrontabSchedule.Parse(_cron);  //解析cron字符串
            }
            catch (Exception e)
            {
                throw;
            }

            while (Enabled && _crontab != null && !stoppingToken.IsCancellationRequested)
            {

                var nextDt = _crontab.GetNextOccurrence(DateTime.Now.AddSeconds(2));

                var interval = (nextDt - DateTime.Now);

                await Task.Delay(interval, stoppingToken);

                var logger = (ILogger)_provider.GetService(typeof(ILogger));

                try
                {
                    logger?.Log(LogLevel.Trace, $"启动计划任务:{this.GetType().Name}");

                    await Run(_provider, stoppingToken);

                    logger?.Log(LogLevel.Trace, $"完成计划任务:{this.GetType().Name}");
                }
                catch (Exception e)
                {
                    logger?.Log(LogLevel.Error, e, $"计划任务执行异常:{e.Message}");
                }
            }
        }

        protected abstract Task Run(IServiceProvider provider, CancellationToken stoppingToken);

        /// <summary>
        /// 计划任务的Cron配置字符串,可使用在线生成器生成后,填入
        /// </summary>
        public virtual string Cron
        {
            get => _cron;
        }

        /// <summary>
        /// 计划任务是否启动
        /// </summary>
        public virtual bool Enabled
        {
            set => _enabled = value;
            get => _enabled;
        }
    }

上述使用类似Timer的方式,,通过计算cron表达式计算后的结果与当前时间差,delay指定时间后触发,这个功能,一般只能用在一些不是特别重要的定时任务,并且不需要补偿的环境下

通常我都是用比如:https://cron.qqe2.com/之类的在线生成cron表达式的网站生成

计划任务使用的第三方组件为:

NCrontab : https://github.com/atifaziz/NCrontab

上述源码地址:

TimerHostedService: https://github.com/kugarliyifan/Kugar.Core/blob/master/Kugar.Core.NetCore/Services/TimerHostedService.cs

SimpleScheduledTaskService : https://github.com/kugarliyifan/Kugar.Core/blob/master/Kugar.Core.NetCore/Services/ScheduledTaskService.cs


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK