C#一个简单的定时表达式(HH:mm:ss)解析
前言
为客户开发了一个日志监控程序,监听各频道是否正常工作。其中有一个功能是这样的,当所有频道正常运行一段时间后,语音提示值班人员系统运行正常。一开始,想法比较简单,设置了一个变量,在线程不断轮询的过程中去统计连续正常运行的总时长,当达到设置的阀值后,提交一条语音播报任务。后来,客户反馈他们需要定点去值班,顺道查看下软件是否正常,听听语音播报。一沟通,好吧,做个类似于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; } …… } }