关于C#中Timer定时器的重入问题解决方法(也适用于多线程)

时间:2022-07-13 20:08:16

项目中用到了定时器随着服务启动作定时任务,按指定的准点时间定时执行相关操作,但是在指定准点时间内我只想让它执行一次,要避免重入问题的发生。

首先简单介绍一下timer,这里所说的timer是指的System.Timers.timer,顾名思义,就是可以在指定的间隔是引发事件。官方介绍在这里,摘抄如下:

Timer 组件是基于服务器的计时器,它使您能够指定在应用程序中引发 Elapsed 事件的周期性间隔。然后可通过处理这个事件来提供常规处理。 例如,假设您有一台关键性服务器,必须每周 7 天、每天 24 小时都保持运行。 可以创建一个使用 Timer 的服务,以定期检查服务器并确保系统开启并在运行。 如果系统不响应,则该服务可以尝试重新启动服务器或通知管理员。
基于服务器的 Timer 是为在多线程环境中用于辅助线程而设计的。 服务器计时器可以在线程间移动来处理引发的 Elapsed 事件,这样就可以比 Windows 计时器更精确地按时引发事件。

那使用这个计时器有啥好处呢?主要因为它是通过.NET Thread Pool实现的、轻量、计时精确、对应用程序及消息没有特别的要求。

Timer是怎么使用的之前有写过这篇:C# System.Timers.Timer定时器的使用和定时自动清理内存应用

什么叫重入呢?这是一个有关多线程编程的概念:程序中,多个线程同时运行时,就可能发生同一个方法被多个进程同时调用的情况。当这个方法中存在一些非线程安全的代码时,方法重入会导致数据不一致的情况。Timer方法重入是指使用多线程计时器,一个Timer处理还没有完成,到了时间,另一Timer还会继续进入该方法进行处理。

关于定时器的重入问题解决方法尝试如下

1、使用锁lock(Object)的方法来防止重入,表示一个Timer处理正在执行,下一个Timer发生的时候发现上一个没有执行完就等待执行,适用重入很少出现的场景。在触发的方法中加入lock,这样当线程2进入触发的方法中,发现已经被锁,会等待锁中的代码处理完在执行,代码如下:

 private static System.Timers.Timer aTimer = new System.Timers.Timer();
private static object loker=new object();
/// <summary>
/// 设置定时器
/// </summary>
public static void SetTimer()
{
//读取配置时间
try
{
aTimer.Interval = ;
aTimer.Elapsed += new System.Timers.ElapsedEventHandler(OnTimedEvent);
aTimer.AutoReset = true;//每到指定时间Elapsed事件是到时间就触发
aTimer.Enabled = true; //指示 Timer 是否应引发 Elapsed 事件。
}
catch (Exception ex)
{
LogManager.RecordLog(LogType.Error, "ipad数据同步出错:" +ex.Message,ex);
}
}
private static void OnTimedEvent(Object source, ElapsedEventArgs e)
{
//如果当前时间是为配置的准点时间就进来
var time =Convert.ToInt32( SynchronousHelper.AppConfig.get_Items("SycTime"));
if (DateTime.Now.Hour == time && DateTime.Now.Minute == )
{
//lock,这样当线程2进入触发的方法中,发现已经被锁,会等待锁中的代码处理完在执行
lock (loker)
{
LogManager.RecordLog(LogType.Info, "数据开始同步时间:" + e.SignalTime, null);
SetTimerStart();
System.Threading.Thread.Sleep(); //执行完越过当前分钟,使整点内只能进来一次
}
}
}

2.置一个标志,表示一个Timer处理正在执行,下一个Timer发生的时候发现上一个没有执行完就放弃(注意这里是放弃,而不是等待哦,看看执行结果就明白啥意思了)执行,适用重入经常出现的场景。在多线程下给inTimer赋值不够安全,Interlocked.Exchange提供了一种轻量级的线程安全的给对象赋值的方法(感觉比较高上大,也是比较推荐的一种方法)。

 private static System.Timers.Timer aTimer = new System.Timers.Timer();
private static int inTimer = ; /// <summary>
/// 设置定时器
/// </summary>
public static void SetTimer()
{
//读取配置时间
try
{
aTimer.Interval = ; //半分钟触发一次
aTimer.Elapsed += new System.Timers.ElapsedEventHandler(OnTimedEvent);
aTimer.AutoReset = true;//每到指定时间Elapsed事件是到时间就触发
aTimer.Enabled = true; //指示 Timer 是否应引发 Elapsed 事件。
}
catch (Exception ex)
{
LogManager.RecordLog(LogType.Error, "ipad数据同步出错:" +ex.Message,ex);
}
}
private static void OnTimedEvent(Object source, ElapsedEventArgs e)
{
//如果当前时间是为配置的准点时间就进来
var time =Convert.ToInt32( SynchronousHelper.AppConfig.get_Items("SycTime"));
if (DateTime.Now.Hour == time && DateTime.Now.Minute == )
{
//inTimer设置一个标志,表示一个Timer处理正在执行,下一个Timer发生的时候发现上一个没有执行完就放弃
if (Interlocked.Exchange(ref inTimer, ) == )
{
LogManager.RecordLog(LogType.Info, "数据开始同步时间:" + e.SignalTime, null);
SetTimerStart();
System.Threading.Thread.Sleep(); //执行完等待越过当前分钟,使整点内只能进来一次
Interlocked.Exchange(ref inTimer, );
}
}
}

稍微总结一下,timer是一个使用挺简单的类,拿来即用,这里主要总结了使用timer时重入问题的解决,以前也没思考过这个问题,解决方案也挺简单。这里的解决方案同时也适用多线程的重入问题。