前言
為客戶開發了一個日志監控程序,監聽各頻道是否正常工作。其中有一個功能是這樣的,當所有頻道正常運行一段時間后,語音提示值班人員系統運行正常。一開始,想法比較簡單,設置了一個變量,在線程不斷輪詢的過程中去統計連續正常運行的總時長,當達到設置的閥值后,提交一條語音播報任務。后來,客戶反饋他們需要定點去值班,順道查看下軟件是否正常,聽聽語音播報。一溝通,好吧,做個類似於cron表達式那樣的定時吧,按照設定的時間規則進行語音播報控制。下面寫寫我的簡單示例,聊以自娛。
一、設置一個定時的配置項
<!-- 系統正常運行定時任務,*:/1:00 表示每小時 每隔1分的時候報--> <SysNormalLong>*:00:00</SysNormalLong>
- 示例1【 *:00:00 】,*表示每小時都執行,分鍾配置成*,則表示每小時都執行。該示例意思是 每小時都整點播報;
- 示例2【 *:/15:00】, /表示配置的是間隔。該示例意思是 每隔15分鍾播報一次;
- 示例3【 12:20:00】,這個意思是每天 12:20:00 播報一次;
二、解析定時配置,生成時分秒對應的驗證函數
private string _sysNormalLong = @"*:00:00"; /// <summary> /// 正常播報時分秒 /// </summary> private string[] _normals; private Func<int, bool>[] _normalFuncs = new Func<int, bool>[3]; /// <summary> /// 單項驗證 /// </summary> /// <param name="val"></param> /// <returns></returns> public Func<int,bool> GetValidFunc(string val) { if (val == "*") return t=>true; int temp = 0; if (val.StartsWith("/")) { //按間隔播報 if (Int32.TryParse(val.Replace("/", ""), out temp)) { return t=> (t%temp==0); } } else { //定點播報 if (Int32.TryParse(val, out temp)) { return t=>t==temp; } } //解析失敗,則按照整點播報的邏輯來做 return t=>t==0; } private SoundWarnThread() { …… _normals = _sysNormalLong.Split(':'); _normalFuncs[0] = GetValidFunc(_normals[0]); _normalFuncs[1] = GetValidFunc(_normals[1]); _normalFuncs[2] = GetValidFunc(_normals[2]); _nextNormalTime = GetNextNormalTime(DateTime.Now.AddSeconds(20)); …… }
主要方法為GetValidFunc。改方法為時分秒每個時間部分都生成一個匿名的判斷函數,確保對配置項做一次解析,避免后期在驗證過程中不斷去分析配置字符串,提升性能。判斷邏輯為:1、當配置*,則都驗證為true;2、當配置以/開頭,則說明配置的是間隔時間,這時從0開始計算,若當前時間是間隔時間的整數倍,則驗證為true;3、配置的是純數字則是固定時間,直接比對是否相等即可。
三、傳入一個時間,驗證當前時間是否符合定時規則
public bool ValidNormal(DateTime now) { //變動快的部分先驗證,代碼執行速度快 bool bS = _normalFuncs[2](now.Second); if (!bS) return false; bool bM = _normalFuncs[1](now.Minute); if (!bM) return false; bool bH = _normalFuncs[0](now.Hour); if (!bH) return false; return true; }
四、獲取最近一次即將到達的定時時間
/// <summary> /// 獲取下次正常播報的時間 /// </summary> /// <returns></returns> public DateTime GetNextNormalTime(DateTime date) { while (!ValidNormal(date)) { date = date.AddSeconds(1); } return date; }
五、判斷某一個時間是否該進行語音播報了(這種方案主要是為了防止線程輪詢時執行時間過長,錯過定時任務執行時間)
public bool VoiceNormal(DateTime now) { if (now >= _nextNormalTime) { _nextNormalTime = GetNextNormalTime(now.AddSeconds(1)); //如果超過1分鍾的偏差都沒能報出正常運行,則認為軟件在這個時間段內捕獲到了異常,丟棄這一次的正常運行播報 if (now - _nextNormalTime > TimeSpan.FromMinutes(1)) return false; _lastNormalTime = now; return true; } return false; }
六、定義一個調用線程
#region 線程處理 private Thread _mainThread = null; public void Start() { _mainThread = new Thread(new ThreadStart(MainProcess)); _mainThread.IsBackground = true; _mainThread.Start(); } /// <summary> /// 正常運行的提示信息 /// </summary> private const string _normalMsg = "播出系統日志監聽正常"; private DateTime? _nextNormalTime; /// <summary> /// 主要的處理邏輯 /// </summary> private void MainProcess() { //1秒讀取一次 TimeSpan interval = TimeSpan.FromSeconds(1); while (true) { if (!WarningQueue.Any()) { Thread.Sleep(interval); if (VoiceNormal(DateTime.Now)) { Voice(_normalMsg); } continue; } …… } }
