設計目標
- 最小間隔 1ms,精度優於
WM_TIMER(~10-15ms)。
- 獨立線程運行,不阻塞主線程。
- 支持任意回調函數
void callback(void*)。
- 動態修改間隔、啟動/停止控制。
- 跨編譯器兼容(MSVC、MinGW)。
核心架構
class LRTimer {
public:
LRTimer(DWORD intervalMs = 1000);
~LRTimer();
void start(); // 用構造/設置的間隔啟動
void start(DWORD intervalMs); // 新間隔啟動
void stop(); // 停止
void setInterval(DWORD ms); // 設置間隔
DWORD getInterval() const;
void setCallback(void(*cb)(void*), void* param); // 設置回調
bool isRunning() const;
private:
static DWORD WINAPI ThreadEntry(void* pThis); // 線程入口
DWORD ThreadProc(); // 實例線程邏輯
static void CALLBACK ApcEntry(void* pThis, DWORD, DWORD); // APC 入口
void ApcProc(); // 實例 APC 邏輯
DWORD interval_; // 間隔(ms)
void(*callback_)(void*); // 回調函數
void* cbParam_; // 回調參數
bool running_; // 運行標誌
HANDLE thread_; // 線程句柄
DWORD threadId_; // 線程ID
};
Windows 可等待定時器機制
- 創建:
CreateWaitableTimer 創建內核定時器對象。
- 設置:
SetWaitableTimer 指定首次觸發時間(相對 100ns 為單位)、週期間隔、APC 回調函數、參數。
- 等待:線程調用
SleepEx(1, TRUE) 進入可提醒等待狀態,APC 排隊執行。
- APC 執行:定時器觸發 → 內核排隊 APC → 線程下次可提醒等待返回 → 執行
ApcProc → 調用用戶回調。
- 清理:
CancelWaitableTimer、CloseHandle。
關鍵實現細節
線程入口適配器
static DWORD WINAPI ThreadEntry(void* pThis) {
return static_cast(pThis)->ThreadProc();
}
線程主循環
DWORD LRTimer::ThreadProc() {
HANDLE timer = CreateWaitableTimer(nullptr, FALSE, name);
LARGE_INTEGER due; due.QuadPart = -interval_ * 10000; // 100ns 為單位,負數=相對時間
SetWaitableTimer(timer, &due, interval_, ApcEntry, this, FALSE);
while (running_)
SleepEx(1, TRUE); // 1ms 精度,TRUE=可提醒
CancelWaitableTimer(timer);
CloseHandle(timer);
return 0;
}
APC 回調適配器
static void CALLBACK ApcEntry(void* pThis, DWORD, DWORD) {
static_cast(pThis)->ApcProc();
}
void LRTimer::ApcProc() {
if (callback_) callback_(cbParam_);
}
CRT 線程兼容啟動
#ifndef _INC_CRTDEFS
thread_ = CreateThread(nullptr, 0, ThreadEntry, this, 0, &threadId_);
#else
thread_ = (HANDLE)_beginthreadex(nullptr, 0,
reinterpret_cast(ThreadEntry),
this, 0, reinterpret_cast(&threadId_));
#endif
使用示例
void myCallback(void* data) {
static int cnt = 0;
const char* sp = "|/-\\";
printf("\r%c", sp[cnt++ % 4]); // 旋轉光標動畫
}
int main() {
LRTimer timer;
timer.setCallback(myCallback, nullptr);
timer.setInterval(50); // 50ms = 20Hz
timer.start();
getchar(); // 運行一段時間
timer.stop();
getchar(); // 驗證已停
timer.start(200); // 修改間隔重啟
getchar();
timer.stop();
return 0;
}
優缺點與適用場景
| 優點 | 缺點 |
| 1ms 精度,無消息泵依賴 | 僅限 Windows 平台 |
| APC 異步回調,不阻塞定時器線程 | 回調執行於定時器線程,不可長時間阻塞 |
| 動態調整間隔、啟停靈活 | 高頻回調(>1kHz)APC 佇列可能擁堵 |
| 輕量級,無 MFC/ATL 依賴 | 需手動管理回調線程安全 |
現代替代方案
- C++20
std::jthread + std::chrono:跨平台,協程友好。
- Boost.Asio
steady_timer:Proactor 模式,高性能網絡/定時統一。
- Windows 線程池定時器:
CreateThreadpoolTimer,系統管理線程,更省資源。
- 多媒體定時器:
timeSetEvent,1ms 精度,回調在系統線程,已棄用建議遷移。
遷移建議
- 新項目優先用 C++20/Boost.Asio 實現跨平台定時器抽象層。
- 現有 Windows 專用代碼可保留 LRTimer,封裝接口便於未來替換後端。
- 回調中耗時操作必須派發到工作線程池,定時器線程僅負責調度。