在项目中遇到一个需求,在整点的时候执行一个方法。(保存数据、处理数据等),此时我们需要一个Timer定时器,当整点的时候可以指定特定事件。那如何解决Timer定时器在整点重复进入方法的问题,继续往下看。{#u8f9d7eb2}
这里我们使用一个实例:当运行到指定时间段的时候,文字开始滚动(实现跑马灯效果){#u22d65c03}
界面效果 {#wHXxR}
在Form窗体中,定义一个label标签,内容是:☆123☆{#u91bcc2df}
代码 {#OiODf}
using System;
using Timers = System.Timers;
using System.Windows.Forms;
using System.Diagnostics;
using System.Threading;
namespace MyTimer
{
public partial class Form1 : Form
{
private Timers.Timer timer1;
private int inTimer;
public Form1()
{
InitializeComponent();
timer1 = new Timers.Timer()
{
Interval = 100,
Enabled = true,
AutoReset = true
};
timer1.Elapsed += Timer1_Elapsed;
this.StartPosition = FormStartPosition.CenterScreen;
}
private void Timer1_Elapsed(object sender, Timers.ElapsedEventArgs e)
{
DateTime currentTime = DateTime.Now;
if(currentTime.Second == 10 || currentTime.Second == 20 || currentTime.Second == 30 || currentTime.Second == 40 || currentTime.Second == 50)
{
#region 第一种方法
//inTimer设置一个标志,表示一个Timer处理正在执行,下一个Timer发生的时候发现上一个没有执行完就放弃
if (Interlocked.Exchange(ref inTimer, 1) == 0)
{
Debug.WriteLine($"数据开始同步时间:{e.SignalTime}");
//第一个重载是从当前索引开始截取后面的字符串 + 第二个重载是从当前索引开始,长度是多少
this.Invoke(new Action(() =>
{
lbl.Text = lbl.Text.Substring(1) + lbl.Text.Substring(0, 1);
}));
System.Threading.Thread.Sleep(60000); //执行完等待越过当前分钟,使整点内只能进来一次
Interlocked.Exchange(ref inTimer, 0);
}
#endregion
#region 第二种方法
if (Math.Abs(currentTime.Millisecond) < 80 )
{
this.Invoke(new Action(() =>
{
lbl.Text = lbl.Text.Substring(1) + lbl.Text.Substring(0, 1);
}));
}
#endregion
}
}
}
`}`
{#uICEA}
代码解析 {#rXzxf}
从当前代码实例解析,首先我们可以知道在窗体初始化的时候给定时器设置了100ms的间隔,也就是说100ms定时器就会触发一次{#u71f1b9ab}
timer正常如果不做任何设置情况下,就是定时器在10,20,30,40,50S的时候会进入这个if判断,在这里面去执行自己的需求方法。此时问题出现:timer的间隔时间为100ms,可能会重复进入多次if语句去执行方法,但是实际需求就是只执行一次方法。{#ud615f5f1}
if(currentTime.Second == 10 || currentTime.Second == 20 || currentTime.Second == 30 || currentTime.Second == 40 || currentTime.Second == 50)
{#GjMZg}
第一种方法------设置标志位 {#k3lz6}
- 设置一个标志位,
private int inTimer;{#ufe77aaab}
- 当inTimer为1的时候表示Timer正在处理,如果此时下一个Timer进入这个判断发现没有执行完就放弃
if(Interlocked.Exchange(ref inTimer, 1) == 0 ){#uc67cf2e5}
-
执行整点需要运行的方法
-
此时可以设置一个延时,执行完越过整点,使整点只能进入一次
-
将inTimer标志位设置为初始状态0Interlocked.Exchange(ref inTimer, 0);
#region 第一种方法 //inTimer设置一个标志,表示一个Timer处理正在执行,下一个Timer发生的时候发现上一个没有执行完就放弃 if (Interlocked.Exchange(ref inTimer, 1) == 0) { Debug.WriteLine($"数据开始同步时间:{e.SignalTime}");
//第一个重载是从当前索引开始截取后面的字符串 + 第二个重载是从当前索引开始,长度是多少 this.Invoke(new Action(() => { lbl.Text = lbl.Text.Substring(1) + lbl.Text.Substring(0, 1); })); System.Threading.Thread.Sleep(60000); //执行完等待越过当前分钟,使整点内只能进来一次 Interlocked.Exchange(ref inTimer, 0);
} #endregion
{#n7x07}
- 优点:简单易懂,适用于简单的时间控制需求。
- 缺点:可能不够精确,受系统时间的影响较大,不适用于需要精确控制的场景。
第二种方法------设置定时器前后进入的误差时间 {#XaUjs}
直接使用Math.Abs方法,判断当前毫秒是否小于80,因为timer的间隔为100,可以保证不会第二次进入。{#u61f6a72d}
#region 第二种方法
if (Math.Abs(currentTime.Millisecond) < 80 )
{
this.Invoke(new Action(() =>
{
lbl.Text = lbl.Text.Substring(1) + lbl.Text.Substring(0, 1);
}));
}
#endregion
{#JQgB7}
- 优点:能够精确控制并发访问,避免竞态条件,适用于多线程环境下的任务调度。
- 缺点:相对较复杂,需要考虑线程安全和并发问题,适用于需要精确控制的场景。