2011-05-05 172 views
7

我有一個無窗口計時器(無WM_TIMER),只有在給定的時間段過去後纔會觸發回調函數。它作爲SetTimer()/KillTimer()實施。時間足夠短:100-300毫秒。SetTimer()陷阱

那麼足夠便宜(我的意思是性能)每隔這麼短的時間間隔撥打SetTimer()/KillTimer()對嗎?

如果我有100個這樣的定時器定期撥打SetTimer()/KillTimer()怎麼辦?系統中可能同時存在多少Window定時器對象?

這就是問題: 使用一堆這樣的計時器對象,並依賴於Windows定時器的良好實現,或者創建一個Windows計時器對象,每隔30毫秒進行一次滴答,並訂閱所有自定義的100-300毫秒定時器。

感謝

回答

2

與定時器消息的問題,因爲你要使用他們的是,它們是低優先級的消息。其實他們是假消息。定時器與底層內核定時器對象相關 - 當消息循環檢測到消息時,它只是用一個標誌標記當前線程消息隊列,該標誌指示下一次調用GetMessage時 - 如果沒有其他消息要處理 - 應該合成WM_TIMER及時回覆消息。

由於可能有很多定時器對象,所以它並不明顯表示系統會公平地爲所有定時器發送定時器消息,任何系統負載都可以完全防止長時間生成WM_TIMER消息。

如果您在控制消息循環,則可以使用維護自己的計時器事件列表(以及GetTickCount時間戳發生時)以及MSGWaitForMultipleObject - 而不是GetMessage等待消息。使用dwTimeout參數來提供最小的時間間隔 - 從現在開始 - 直到下一個計時器應該發出信號。因此,每次有計時器處理時,它都會從等待消息中返回。

和/或您可以使用waitable timers - 無論是在MSGWaitForMultipleObjects的GUI線程上,還是僅在工作線程上,都可以直接訪問低層定時功能。

+1

我不使用WM_TIMER。我使用無窗計時器(傳遞時間proc指針直接:: SetTimer())。問題是關於如何集中創建/銷燬定時器內核對象以及多少這樣的對象可能同時存在而沒有任何問題是很昂貴的。謝謝。 – Stas 2011-05-05 08:22:45

+4

您總是使用WM_TIMER。 GetMessage合成消息,而DispatchMessage - 當傳遞帶有空hwnd的WM_TIMER消息時,將其直接分發給TimerProc/LPARAM。 – 2011-05-05 09:15:13

+1

我不確定「當消息循環檢測到火災時」的含義。它的工作方式是系統線程管理用戶計時器的完整列表,並執行火災檢測並設置標誌以喚醒正確的線程。 GetMessage將看到該標誌,如果沒有其他消息存在,必須去實際挖掘該定時器。線程唯一知道的是1)計時器被觸發,2)被觸發多少次。 GetMessage必須實際進行一次調用才能掃描整個用戶計時器列表,並找到與您的線程關聯的最早的一個。你的下一個GetMessage發現下一個,等等。 – James 2017-11-28 07:43:59

2

最大SetTimer()缺陷是,實際上它是用戶對象(儘管它不是在MSDN USER objects list列出的事實),因此它屬於Windows用戶下的對象限制 - 默認最高每處理10000個對象,每個會話最多65535個對象(所有運行的進程)。

這可以很容易地通過簡單的測試證明 - 只需調用SetTimer()(參數不關心,窗口和無窗口都以相同的方式進行操作),並查看任務管理器中增加的USER對象數。

另請參閱ReactOS ntuser.h來源和this article。他們都聲明TYPE_TIMER是USER句柄類型之一。

因此,小心 - 創建一堆定時器可能會耗盡您的系統資源,並使您的進程崩潰甚至整個系統無響應。

0

這裏是我覺得你以後實際上是同時問這個問題的細節:

SetTimer的()會先掃描非內核定時器列表(雙向鏈表),看看是否計時器ID已​​經存在。如果定時器存在,它將被簡單地重置。如果不是,則發生HMAllocObject調用併爲該結構創建空間。計時器結構將被填充並鏈接到列表的頭部。

這將是創建每個100個定時器的總開銷。這正是例程所做的,除了檢查最小和最大dwElapsed參數。只要定時器到期,定時器列表就會在(大約)最後定時器列表掃描期間看到的最小定時器持續時間的持續時間內掃描。 (實際上,真正發生的是 - 內核定時器被設置爲找到的最小用戶定時器的持續時間,並且該內核定時器喚醒線程,該線程檢查用戶定時器到期並通過設置標誌來喚醒相應的線程他們的消息隊列狀態)。

對於列表中的每個定時器,當前時間(以毫秒爲單位)時,從列表中的每個定時器遞減當前時間(以毫秒爲單位)。當到期時(< = 0),它在自己的結構中被標記爲「就緒」,並且從定時器結構讀取線程信息的指針,並通過設置線程的QS_TIMER標誌來喚醒相應的線程。然後它增加你的消息隊列的CurrentTimersReady計數器。這就是所有計時器到期時間。沒有發佈實際消息。

當你的主消息泵調用的GetMessage(),當沒有其他可用的消息,GetMessage函數()檢查您的線程的喚醒位QS_TIMER,如果設置 - 生成由掃描完整的用戶定時器列表中的WM_TIMER消息列表中標記爲READY並且與您的線程ID相關聯的最小定時器。然後它減少線程的CurrentTimersReady計數,如果爲0,則清除定時器喚醒位。下一次調用GetMessage()將導致相同的事情發生,直到所有計時器耗盡。

一次性定時器保持實例化。當它們到期時,它們被標記爲等待。下一次調用具有相同計時器ID的SetTimer()將僅更新並重新激活原始。無論是一個鏡頭還是週期性定時器都會自行重置,並且只有在KillTimer或您的線程或窗口被銷燬時纔會死機。

Windows的實現是非常基礎的,我認爲編寫更高性能的實現對你來說是微不足道的。