一直想寫這一篇關於很久以前時間人系列的補遺,想起當時寫文章太不夠謹慎,對某些基本概念的認識不足,至今仍梗梗於懷;偏偏我又是個懶人,兼之一直忙著寫其它內容,然後就把這事忘了。不過今天有位熱心的網友回應了那一篇的問題 ( 終於被逮到了 ),讓我立下決心今天把這篇補遺寫完。
一直想寫這一篇關於很久以前時間人系列的補遺,想起當時寫文章太不夠謹慎,對某些基本概念的認識不足,至今仍梗梗於懷;偏偏我又是個懶人,兼之一直忙著寫其它內容,然後就把這事忘了。不過今天有位熱心的網友回應了那一篇的問題 ( 終於被逮到了 ),讓我立下決心今天把這篇補遺寫完。
很多年前,當時我還不知道什麼是 MVP 的時候寫了一篇 三種時間人《.NET中的Timer(1)》 ,其中有一些謬誤和補充的部份要在這一篇當中說明。
第一個要說明的是關於 Application.Doevents() 造成的影響,在 Windows Forms 的架構上,Forms.Timers 是倚賴事件訊息佇列的。當一個 Forms.Timer 的 Tick 事件發生時,系統會將此 Tick 事件排入訊息佇列中,然後依照順序執行已排入事件的事件委派函式,所以正確來說,在無外力影響的狀況下,它會依序執行事件。但這其中有個弔詭的地方,假設你的 Timer Interval 為 100 ms ,而你的事件委派函式的執行時間需要 200 ms,第二個 Tick (含) 以後的執行就會一路往後延,類似下表 (當然那數字不是很精確,因為有時間解析度的誤差):
事件順序 | 發生時間 |
1 | 100ms |
2 | 300ms |
3 | 500ms |
從上面的表可以看出來,原先你所預期的可能是 100ms, 200ms, 300ms …這樣的順序,但事實上執行委派函式的時間卻是 100ms, 300ms, 500ms …,事件排入佇列的時間和執行委派函式的時間會發生明顯的不一致情形,所以用 Forms.Timer 還是得注意你的委派函式會花多久時間執行。
Application.DoEvents() 在這邊產生了什麼影響?Application.DoEvents() 會立刻將已在佇列中的事件取出處理,意思就是說它將會改變你程式的流程,我之前文章上的謬誤就是這樣發生的,因為它執行到 Application.DoEvents() 時就將佇列中的事件取出並執行其委派函式,於是流程就變成以堆疊的方式存放。基本上的建議是:除非逼不得已,不然 Application.Doevents() 應該少用,使用時也應該多考慮到它對於你程式流程的影響。
關於預設時間解析度,這問題有點麻煩的是並不是每個作業系統的時間解析度都是相同的,而且很多資料都找不太到正式的來源,目前 MSDN 上關於 Timer 的最新文件是說明 Windows 7 的預設時間解析度為 15.6ms ,也就是說在預設的狀況下你將 Timer 的 Interval 設到 15.6 ms 以下是沒什麼意義的,關於Windows 時間的詳細說明可以參考以下幾份文件:
(1)Timers, Timer Resolution, and Development of Efficient Code 這篇文件詳解了 Windows 7 的 Timer 解析度及其運作方式,以及如何修改解析度。
(2)Windows Time 這是較早前的文件,其中提到 『approximately 10 milliseconds to 16 milliseconds』,感謝璉大的指點,這段表示在 Windows NT 到 Vista 的部份應該都是在 10 ms 左右。
(3)Implement a Continuously Updating, High-Resolution Time Provider for Windows
另外璉大還特別告訴我在 Windows 9x 時代的預設時間解析度應該是在 54~55 ms 左右。
這就是關於之前時間人系列一直想要補充及修正的部份,總算了了一樁心事,感恩網友的回應,促使我趕快寫完這篇補遺。