.NET 提供了三种Timer:
基于服务的Timer System.Timers.Timer
基于线程的Timer System.Threading.Timer
基于窗体的Timer System.Windows.Forms.Timer
1、System.Threading.Timer,希望在另一个线程上定时执行后台任务时,这个定时器是最好的定时器。
2、System.Windows.Forms.Timer。构造该类的实例可以告诉windows将定时器和调用线程相关联。随着定时器的触发,Windows将一个定时器消息(WM_TIMER)插入到线程的消息队列中,调用线程必须执行一个消息泵(message pump),从而提取消息,并将它们分配到期望的回调方法中。所有这些工作都是由一个线程完成的,设置定时器的线程保证时执行回调方法的线程。这就意味着定时器方法不能别多个线程同时调用。
3、System.Timers.Timer。这个定时器基本上是对Threading的Timer类的包装,当定时器时间到期后,将导致CLR将事件加入线程池的队列。此类派生自Sys.ComponentModel的Component类,Component类允许将这些定时器放到VS设计界面上。因此如果希望在设计界面上使用定时器,可以使用这个。一般情况下,还是使用Threading的Timer类。
其中Windows Timers只是提供了和WinAPI 一样的Timer,仍然是基于消息,是单线程的。其它两个是基于线程池的Thread Pool,这样产生的时间间隔准确均匀。
单线程Timer问题
Win32 API中有个SetTimer函数,可以为一个窗口创建一个定时器,这个定时器会定时产生消息WM_TIMER也可以调用指定的回调函数,但是是单线程的。
单线程的定时器会有很多问题,首先是不准时,定时器只是定时把消息WM_TIMER访到线程的消息队列里,但是并不保证消息会立刻被响应,如果碰巧系统比较忙,那么消息可能会在队列里放一端时间才被响应,还会造成本来应该间隔一段时间发生的消息响应连续发生。
解决方法通常是
OnTimer(...)
{
//Timer process.....
MSG msg;
While(PeekMessage(&msg, m_hWnd, WM_TIMER, WM_TIMER, PM_REMOVE));
}
在当前Timer处理中,把消息队列里的WM_TIMER消息,清除掉。但是如果你不去调用GetMessage,那么就不会有Timer发生了。
多线程Timer重入问题
由于使用多线程定时器,就会出现如果一个Timer处理没有完成,到了时间下一个照样会发生,这就会导致重入。
对付重入问题通常的办法是加锁,但是对于 Timer却不能简单的这样做,你需要评估一下。
首先Timer处理里本来就不应该做太需要时间的事情,或者花费时间无法估计的事情,比同远方的服务器建立一个网络连接,这样的做法尽量避免。
如果实在无法避免,那么要评估Timer处理超时是否经常发生,如果是很少出现,那么可以用lock(Object)的方法来防止重入。
如果这种情况经常出现呢?那就要用另外的方法来防止重入。
可以设置一个标志,表示一个Timer处理正在执行,下一个Timer发生的时候发现上一个没有执行完就放弃执行。
static int Finished= 0;
public static void threadTimerCallback(Object obj)
{
if ( Finished== 0 )
{
Finished= 1;
Thread.Sleep(2000);
Finished= 0;
}
}
但是在多线程下给inTimer赋值不够安全。
Interlocked.Exchange提供了一种轻量级的线程安全的给对象赋值的方法。
static int Finished= 0;
public static void threadTimerCallback(Object obj)
{
if ( Interlocked.Exchange(ref Finished, 1) == 0 )
{
Thread.Sleep(250);
Interlocked.Exchange(ref Finished, 0);
}
}