C#編寫一個在asp.net core 3.1下的簡單的corn模式的計劃任務和一個更簡單的定時器類

  • 2021 年 1 月 31 日
  • 筆記

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指定時間後觸發,這個功能,一般只能用在一些不是特別重要的定時任務,並且不需要補償的環境下

通常我都是用比如://cron.qqe2.com/之類的在線生成cron表達式的網站生成

 

計劃任務使用的第三方組件為:

NCrontab : //github.com/atifaziz/NCrontab

 

上述源碼地址:

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

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