首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

VC 多定時器實現詳解:SetTimer/KillTimer、WM_TIMER 消息處理、回調 vs 消息隊列、實戰調度器模式

     編輯:關於C語言

Windows 定時器機制:兩種模式

模式API觸發方式精度/延遲適用場景
消息隊列模式SetTimer(hWnd, id, elapse, NULL)發送 WM_TIMER 到消息隊列低優先級,可能延遲 (~10-50ms)UI 更新、非關鍵周期檢查
回調函數模式SetTimer(hWnd, id, elapse, TimerProc)直接調用回調函數較低延遲,但需線程安全高精度、非 UI 線程

MFC 封裝:CWnd::SetTimer / KillTimer


// 啟動定時器
UINT_PTR SetTimer(UINT_PTR nIDEvent, UINT nElapse, void (CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD));

// 停止定時器
BOOL KillTimer(UINT_PTR nIDEvent);
  • nIDEvent:定時器 ID,同一窗口內唯一,用於 WM_TIMER 中區分。
  • nElapse:間隔毫秒數 (最小 ~10-15ms,受系統時鐘分辨率限制)。
  • lpfnTimer = NULL:使用消息隊列模式 (推薦 UI 線程)。

多定時器實戰:啟動、分派、停止


class CMyDlg : public CDialog {
    // ...
    void StartTimers() {
        SetTimer(1, 500, NULL);  // ID=1, 500ms
        SetTimer(2, 1000, NULL); // ID=2, 1000ms
        SetTimer(3, 2000, NULL); // ID=3, 2000ms
    }

    void StopTimers() {
        KillTimer(1);
        KillTimer(2);
        KillTimer(3);
    }

    // WM_TIMER 處理器 (ClassWizard 添加)
    void OnTimer(UINT_PTR nIDEvent) {
        switch (nIDEvent) {
            case 1: OnTimer500ms(); break;
            case 2: OnTimer1s(); break;
            case 3: OnTimer2s(); break;
        }
        CDialog::OnTimer(nIDEvent);
    }

    void OnTimer500ms() { /* 高頻檢查 */ }
    void OnTimer1s()    { /* 秒級更新 UI */ }
    void OnTimer2s()    { /* 低頻後台任務 */ }
};

關鍵細節與最佳實踐

1. 定時器資源有限,檢查返回值


if (SetTimer(1, 100, NULL) == 0) {
    AfxMessageBox(_T("系統定時器資源耗盡!"));
}

2. WM_TIMER 低優先級特性

  • 消息隊列中有高優先級消息 (輸入、繪製) 時,WM_TIMER 會被延遲處理。
  • 多個 WM_TIMER 可能合併 (若處理太慢),導致丟失觸發。
  • 嚴格實時需求請用多媒體定時器 (timeSetEvent) 或高精度計時器 (QueryPerformanceCounter + 等待)。

3. 重新設置間隔:同 ID 再次 SetTimer


SetTimer(1, 500, NULL); // 500ms
// 後續動態改為 200ms
SetTimer(1, 200, NULL); // 修改現有定時器間隔

4. 銷毀窗口時必須清理


void CMyDlg::OnDestroy() {
    KillTimer(1); KillTimer(2); KillTimer(3);
    CDialog::OnDestroy();
}

實戰案例:排程提醒器 (類原文 ScheduleView)


void CScheduleView::OnTimer(UINT nIDEvent) {
    if (nIDEvent != 1) return; // 僅處理 ID=1

    CTime now = CTime::GetCurrentTime();
    CTimeSpan tolerance(0, 0, 15, 0); // 15秒容差

    CScheduleDoc* pDoc = GetDocument();
    ScheduleItem* pUp = pDoc->m_pNearestTaskUp;   // 最近已過期任務
    ScheduleItem* pDown = pDoc->m_pNearestTaskDown; // 最近即將到來任務

    // 檢查是否有任務狀態變化
    bool needUpdate = false;
    if (pUp && pUp->ti < now - tolerance) needUpdate = true;
    if (pDown && pDown->ti <= now + tolerance) needUpdate = true;

    if (needUpdate) {
        pDoc->SearchNearestTask(); // 重新計算最近任務
        pDoc->UpdateState();       // 更新任務狀態
        pDoc->UpdateAllViews(NULL, 0, NULL); // 刷新視圖
    }

    // 即將到來提醒:彈窗並置頂
    if (pDown && pDown->ti <= now + tolerance) {
        AfxGetMainWnd()->ShowWindow(SW_RESTORE);
        SetForegroundWindow();
        CString msg = _T("提醒: \"") + pDown->des + _T("\" 即將開始!");
        AfxMessageBox(msg);
    }
}

進階:多媒體定時器 (高精度)


#include 
#pragma comment(lib, "winmm.lib")

MMRESULT timerId;
void CALLBACK TimeProc(UINT uTimerID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2) {
    // 高精度回調 (1ms 分辨率)
}

void StartHighResTimer() {
    TIMECAPS tc; timeGetDevCaps(&tc, sizeof(tc));
    timerId = timeSetEvent(1, tc.wPeriodMin, TimeProc, 0, TIME_PERIODIC);
}

void StopHighResTimer() {
    timeKillEvent(timerId);
}

避坑指南

  • ❌ 忘記 KillTimer → 定時器洩漏,資源耗盡。
  • ❌ WM_TIMER 處理耗時過長 → 阻塞 UI 線程,導致消息堆積、定時器合併/丟失。
  • ❌ 假設定時器精確觸發 → 實際有誤差,邏輯需容差。
  • ❌ 回調模式中操作 UI/非線程安全資源 → 崩潰/死鎖。回調運行在系統定時器線程。
  • ❌ SetTimer 返回 0 未檢查 → 靜默失效。

總結

VC 多定時器核心:SetTimer(id, interval, NULL) 啟動消息隊列模式、OnTimer(id) 分派處理、KillTimer(id) 清理。ID 區分多定時器,間隔單位毫秒。WM_TIMER 低優先級、非實時,嚴格場景用多媒體定時器。窗口銷毀必清理,返回值必檢查。掌握此模式,實現周期性 UI 更新、後台輪詢、排程提醒等功能得心應手。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved