在開發 Windows 下的應用程序時,經常需要用的計時,尤其在一些對時間要求比較高的程序中,計時的精確性是很重要的,本文介紹了兩種精確計時的方法,計時的精度可以達到ms級,而且可以認為它是精確的,可以在大多數情況下作為時間的基准。
用API函數::timeGetTime()獲取從開機到現在經過的ms數,它的返回類型為DWORD類型,因此它的最大計時長度為2^32ms,約等於49天,::timeGetTime()是一個多媒體函數,所以它的優先級是很高的,一般可以將它看成是精確的。
用查詢系統定時器的計數值的方法,用到的API函數是QueryPerformanceCounter、QueryPerformanceFrequency,方法是用當前計數值減去開始計時時刻的計數值,得到計數差值,再除以系統定時器的頻率就是計的時間,通常系統定時器的頻率非常高,我在 intel845e 的主板上達到了3579545hz,當然對於不同的主板,它的頻率是不同的。程序運行的結果 如圖一所示:
圖一
這種計時方法要用另外一個線程專門來查詢系統定時器的計數值,這就用到了多線程的知識。由於線程的調用是需要處理器時間的,所以在本中,多線程定時器的時間總要落後於多媒體定時器時間。但在中間的任何一個讀取時間的時刻都是非常精確的,只是從讀取到顯示有一個延遲過程。
下面講一下Windows高頻事件的產生,還是利用上面兩種方法,Windows下有一個多媒體定時器,用法為一組API函數的調用,它們是:
MMRESULT timeBeginPeriod( UINT uPeriod ) ;
MMRESULT timeSetEvent( UINT uDelay,
UINT uResolution,
LPTIMECALLBACK lpTimeProc,
DWORD dwUser,
UINT fuEvent
);
void CALLBACK TimeProc( UINT uID,
UINT uMsg,
DWORD dwUser,
DWORD dw1,
DWORD dw2
);
MMRESULT timeKillEvent( UINT uTimerID );
MMRESULT timeEndPeriod( UINT uPeriod );
其中timeBeginPeriod是用來設置最高定時精度的,最高精度為1ms,如果要產生間隔為1ms的中斷,必須調用timeBeginPeriod(1);當定時器用完之後就要用timeEndPeriod(1);來恢復默認的精度。具體使用方法為在timeBeginPeriod(1)調用之後用timeSetEvent()注冊一個回調函數,即一個中斷處理過程。它還可以向回調函數傳遞一個參數,通常可以傳送一個窗口句柄之類的東西。而回調函數TimeProc則從dwwUser參數中取出傳遞的參數使用。在Windows下,可以用這種方法進行1ms精度的定時數據采集,數據發送,但要保證1ms能完成所有的操作和運算。本人經過實踐證明,用它來實現控制的精度是足夠的。
第二種方法還是使用多線程查詢系統定時器計數值,它與上面提到的方法相比有優點也有缺點,缺點是精度不夠高,優點是產生的間隔能突破1ms的限制,可以達到更小的間隔,理論上事件產生的頻率可以和系統定時器的頻率一樣。主要示例代碼如下:
UINT Timer(LPVOID pParam)
{
QueryPerformanceCounter((LARGE_INTEGER *)& gl_BeginTime );
while(gl_bStart)
{
QueryPerformanceCounter((LARGE_INTEGER *)&gl_CurrentTime );
If(gl_CurrentTime - gl_BeginTime > 1.0/Interval )
{
//定時的事件,比如發送數據到端口,采集數據等
gl_BeginTime = gl_CurrentTime;
}
}
return 1;
}
這是多線程中的一個線程函數,Interval是產生事件的間隔,如果為0.001則為1ms產生一次,理論上如果Interval為1,則以最大的頻率產生事件。即可以用Windows產生很高頻率的事件,但是由於線程的調用是要有時間的,有的時候可能會造成這個線程一直沒有得到執行,從而造成有一段時間沒有進行計數,這段時間的定時事件就沒有產生了,如果定時的頻率越高,丟失的可能性就越大。但如果用它來產生高頻隨時間變化的隨機信號還是很有價值的。這在實時仿真中尤其如此。
具體的實現請參看詳細的例子代碼。
本文配套源碼