C# timer定时器小语

478 阅读1分钟

起因

最近在做一个PLC的工控项目,CEMS。数据1分钟一次入库。
起初我使用了定时器,后来发现系统运行时间久了,会出现缺失数据的情况
这就比较尴尬了,对于CEMS来说,数据完整性是第一要务。

分析

我使用的时system.timer 的定时器。定时器到点,会触发到点事件,并执行到点处理函数。
并且:基于 System.Timers.Timer 服务器的类设计用于多线程环境中的工作线程。 服务器计时器可以在线程之间移动以处理引发Elapsed的事件,从而比Windows计时器在及时引发事件时更准确。
ok,这么一分析,说明timer不背锅啊,不管timer event处理函数中如何复杂。不会影响timer的运行。

测试

定时1s打印时间

制作一个简单测试demo

using System;
using System.Threading;
using System.Timers;

namespace TesTimer
{
    internal class Program
    {

        /// <summary>
        /// 入口
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            StartMainTimer();
            while (true)
            {
                Thread.Sleep(5);
            }
        }

        #region 5s 定时器回调
        /// <summary>
        /// Timer类执行定时到点事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void TimerOutCallBack(object sender, ElapsedEventArgs e)
        {
            Console.WriteLine($"---->   [{DateTime.Now.ToString("HH:mm:ss:fff")}]");
        }

        /// <summary>
        /// 启动timer
        /// </summary>
        static void StartMainTimer()
        {
            System.Timers.Timer Read_Timer;
            //设置定时间隔(毫秒为单位) 5s
            int interval = 1 * 1000;
            Read_Timer = new System.Timers.Timer(interval);
            Read_Timer.AutoReset = true;
            Read_Timer.Enabled = true;
            Read_Timer.Elapsed += new ElapsedEventHandler((s, e) => TimerOutCallBack(s, e));
            Read_Timer.Start();
        }
        #endregion
    }
}

运行结果如下:

image.png timer定时确实不是准确的1s(这个和系统的时钟,但是s位置可以保持1s),接下来我们在timer的回调中做一个延时,看是否会影响timer的运行。

修改

在timer中增加一个readline

        /// <summary>
        /// Timer类执行定时到点事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void TimerOutCallBack(object sender, ElapsedEventArgs e)
        {
            Index++;
            Console.WriteLine($"[{Index.ToString()}]callback---->   [{DateTime.Now.ToString("HH:mm:ss:fff")}]");
            Console.ReadLine();
            Console.WriteLine($"[{Index.ToString()}]---->   [{DateTime.Now.ToString("HH:mm:ss:fff")}]");
        }

测试: 可以看到丢失一个15S的数据

image.png

另外,根据以下

image.png

可以看出,timer定时后启动的线程并没有退出,而是阻塞在了console.readline,在后台运行。

callback多个处理函数

system.timer可以追加多个事件处理函数

        /// <summary>
        /// 启动timer
        /// </summary>
        static void StartMainTimer()
        {
            System.Timers.Timer Read_Timer;
            //设置定时间隔(毫秒为单位) 5s
            int interval = 1 * 1000;
            Read_Timer = new System.Timers.Timer(interval);
            Read_Timer.AutoReset = true;
            Read_Timer.Enabled = true;
            Read_Timer.Elapsed += new ElapsedEventHandler((s, e) => TimerOutCallBack(s, e));
            Read_Timer.Elapsed += new ElapsedEventHandler((s, e) => TimerOutCallBack2(s, e));
            Read_Timer.Start();
        }
        /// <summary>
        /// Timer类执行定时到点事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void TimerOutCallBack2(object sender, ElapsedEventArgs e)
        {
            int temp = CallBack1;
            Console.WriteLine($"[{temp.ToString()}]callback2---->   [{DateTime.Now.ToString("HH:mm:ss:fff")}]");
            Console.WriteLine($"[{temp.ToString()}]---->   [{DateTime.Now.ToString("HH:mm:ss:fff")}]");
        }
        /// <summary>
        /// Timer类执行定时到点事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void TimerOutCallBack(object sender, ElapsedEventArgs e)
        {
            Index++;
            int temp = Index;
            Console.WriteLine($"[{temp.ToString()}]callback---->   [{DateTime.Now.ToString("HH:mm:ss:fff")}]");
            Console.ReadLine();
            CallBack1++;
            Console.WriteLine($"[{temp.ToString()}]---->   [{DateTime.Now.ToString("HH:mm:ss:fff")}]");
        }

那么,注册两个事件处理函数,是启动两个线程呢?还是顺序调用呢?

image.png

可以说明,timer到点后,首先调用第一个注册的函数。如果第一个函数阻塞,第二个回调不会执行,一直到第一个函数阻塞完毕。