• 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;
                    }
                    ……
                }
            }
  • 相关阅读:
    Spring如何解决循环依赖
    AbstractQueuedSynchronizer之AQS
    Spring中各种扩展原理及容器创建原理
    SpringAOP和TX事务的源码流程
    Spring的IOC常用注解(含源码)
    采用lua脚本获取mysql、redis数据以及jwt的校验
    Redis常用数据类型及其存储结构(源码篇)
    Redis分布式锁
    雪花算法
    springboot2.2.6项目接入Nacos流程
  • 原文地址:https://www.cnblogs.com/MrSi/p/8488582.html
Copyright © 2020-2023  润新知