定時器是個很有意思的東西,它很有用,但我認(rèn)為這不是現(xiàn)代計(jì)算機(jī)的結(jié)構(gòu)所擅長的事情。
計(jì)算機(jī)適合做那些很大量的簡單重復(fù)工作,或者根據(jù)請求做出回應(yīng)。
DOS時代是沒有進(jìn)程線程等概念的,那時候要想做到定時真是有些麻煩
通常的做法是死循環(huán)不斷監(jiān)測時間,發(fā)現(xiàn)時間到了就做特定的事情
當(dāng)然你可以用delay,來指定等待多長時間,但是如果你一邊要響應(yīng)用戶的操作,比如輸入,一邊要定時做些
事情就是一件麻煩的事了
當(dāng)然有些人可以這樣做,截取系統(tǒng)的時鐘中斷(我忘了中斷號是多少了),每秒鐘有18.2次
當(dāng)這些做法都不是很優(yōu)雅。但DOS時代只能這樣湊合著了
Windows是個偉大的進(jìn)步,系統(tǒng)提供了Timer支持,但是問題是這個定時器并不準(zhǔn)時而且有時候根本不能用。
Win32 API中有個SetTimer函數(shù),可以為一個窗口創(chuàng)建一個定時器,這個定時器會定時產(chǎn)生消息WM_TIMER也可以調(diào)用
指定的回調(diào)函數(shù),其實(shí)這都是一樣的,因?yàn)槎际菃尉€程的。
單線程的定時器會有很多問題,首先是不準(zhǔn)時,定時器只是定時把消息WM_TIMER訪到線程的消息隊(duì)列里,但是并不保證消息會立刻被響應(yīng),如果
碰巧系統(tǒng)比較忙,那么消息可能會在隊(duì)列里放一端時間才被響應(yīng),還會造成本來應(yīng)該間隔一段時間發(fā)生的消息響應(yīng)連續(xù)發(fā)生了
解決方法通常是
OnTimer(...)
{
//Timer process.....
MSG msg;
While(PeekMessage(&msg, m_hWnd, WM_TIMER, WM_TIMER, PM_REMOVE));
}
在當(dāng)前Timer處理中,把消息隊(duì)列里的WM_TIMER消息,清除掉。
更糟的是如果你不去調(diào)用GetMessage,那么就不會有Timer發(fā)生了。
這個問題直到win xp都沒什么改變,似乎微軟并不打算在Win32 API中解決這個問題了。
.NET Framework為我們帶來了新的解決方案
.NET Framework提供三種Timer
Server Timers System.Timers.Timer
Thread Timers System.Threading.Timer
Windows Timers System.Windows.Forms.Timer
其中Windows Timers只是提供了和WinAPI 一樣的Timer,仍然是基于消息,仍然是單線程
其它兩個就不同了,他們是基于線程池的Thread Pool,這樣最大的好處在于,產(chǎn)生的時間間隔準(zhǔn)確均勻。
Server Timers 和 Thread Timers 的不同在于ServerTimers 是基于事件的,Thread Timers是基于回調(diào)函數(shù)
我更喜歡Thread Timer,比較輕量級方便易用。
但是這樣的Timer也有問題,就是由于時多線程定時器,就會出現(xiàn)如果一個Timer處理沒有完成,到了時間下一個
照樣會發(fā)生,這就會導(dǎo)致重入問題
對付重入問題通常的辦法是加鎖,但是對于 Timer卻不能簡單的這樣做,你需要評估一下
首先Timer處理里本來就不應(yīng)該做太需要時間的事情,或者花費(fèi)時間無法估計(jì)的事情,比同遠(yuǎn)方的服務(wù)器建立一個網(wǎng)絡(luò)連接,這樣的做法盡量避免
如果實(shí)在無法避免,那么要評估Timer處理超時是否經(jīng)常發(fā)生,如果是很少出現(xiàn),那么可以用lock(Object)的方法來防止重入
如果這種情況經(jīng)常出現(xiàn)呢?那就要用另外的方法來防止重入了
我們可以設(shè)置一個標(biāo)志,表示一個Timer處理正在執(zhí)行,下一個Timer發(fā)生的時候發(fā)現(xiàn)上一個沒有執(zhí)行完就放棄執(zhí)行
static int inTimer = 0;
public static void threadTimerCallback(Object obj)
{
if ( inTiemr == 0 )
{
inTimer = 1;
Console.WriteLine("Time:{0}, \tThread ID:{1}", DateTime.Now, Thread.CurrentThread.GetHashCode());
Thread.Sleep(2000);
inTimer = 0;
}
}
但是在多線程下給inTimer賦值不夠安全,還好Interlocked.Exchange提供了一種輕量級的線程安全的給對象賦值的方法
static int inTimer = 0;
public static void threadTimerCallback(Object obj)
{
if ( Interlocked.Exchange(ref inTimer, 1) == 0 )
{
Console.WriteLine("Time:{0}, \tThread ID:{1}", DateTime.Now, Thread.CurrentThread.GetHashCode());
Thread.Sleep(250);
Interlocked.Exchange(ref inTimer, 0);
}
}