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