本文最后更新于272 天前,其中的信息可能已经过时,如有错误请发送邮件到2289035571@QQ.COM
感谢 TurboAI对本博客的的大力赞助。 创作不易,如果您觉得有帮助,请 支持LIncol29! 为了让我能够继续创作更好的内容,你也可以选择订阅博客的 VIP ,包年VIP仅需10元/年,所有VIP内容免费观看
在项目中遇到一个需求,在整点的时候执行一个方法。(保存数据、处理数据等),此时我们需要一个Timer定时器,当整点的时候可以指定特定事件。那如何解决Timer定时器在整点重复进入方法的问题,继续往下看。
这里我们使用一个实例:当运行到指定时间段的时候,文字开始滚动(实现跑马灯效果)
界面效果
在Form窗体中,定义一个label标签,内容是:☆123☆
代码
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
}
}
}
}
代码解析
从当前代码实例解析,首先我们可以知道在窗体初始化的时候给定时器设置了100ms的间隔,也就是说100ms定时器就会触发一次
timer正常如果不做任何设置情况下,就是定时器在10,20,30,40,50S的时候会进入这个if判断,在这里面去执行自己的需求方法。此时问题出现:timer的间隔时间为100ms,可能会重复进入多次if语句去执行方法,但是实际需求就是只执行一次方法。
if(currentTime.Second == 10 || currentTime.Second == 20 || currentTime.Second == 30 || currentTime.Second == 40 || currentTime.Second == 50)
第一种方法——设置标志位
- 设置一个标志位,
private int inTimer;
- 当inTimer为1的时候表示Timer正在处理,如果此时下一个Timer进入这个判断发现没有执行完就放弃
if(Interlocked.Exchange(ref inTimer, 1) == 0 )
- 执行整点需要运行的方法
- 此时可以设置一个延时,执行完越过整点,使整点只能进入一次
- 将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
- 优点:简单易懂,适用于简单的时间控制需求。
- 缺点:可能不够精确,受系统时间的影响较大,不适用于需要精确控制的场景。
第二种方法——设置定时器前后进入的误差时间
直接使用Math.Abs方法,判断当前毫秒是否小于80,因为timer的间隔为100,可以保证不会第二次进入。
#region 第二种方法
if (Math.Abs(currentTime.Millisecond) < 80 )
{
this.Invoke(new Action(() =>
{
lbl.Text = lbl.Text.Substring(1) + lbl.Text.Substring(0, 1);
}));
}
#endregion
- 优点:能够精确控制并发访问,避免竞态条件,适用于多线程环境下的任务调度。
- 缺点:相对较复杂,需要考虑线程安全和并发问题,适用于需要精确控制的场景。
这个非常的好用,支持一下