起因
最近在做一个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
}
}
运行结果如下:
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的数据
另外,根据以下
可以看出,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")}]");
}
那么,注册两个事件处理函数,是启动两个线程呢?还是顺序调用呢?
可以说明,timer到点后,首先调用第一个注册的函数。如果第一个函数阻塞,第二个回调不会执行,一直到第一个函数阻塞完毕。