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

C# 高精度計時器自製實戰:突破 15ms 系統限制,達到微秒級排程精度

     編輯:關於C語言

為什麼標準 Timer 不夠用?

Windows 非即時作業系統,預設時鐘中斷週期 15.625ms。三大標準 Timer (Forms.Timer, Timers.Timer, Threading.Timer) 皆建立在此基礎上,實際精度難以穩定在 10ms 以下。媒體播放、遊戲迴圈、硬體控制、效能分析等場景需要 1ms 甚至亞毫秒級 精度。

核心構建塊:等待策略與計時基準

計時基準:Stopwatch (唯一選擇)

  • 底層呼叫 QueryPerformanceCounter / QueryPerformanceFrequency
  • 硬體級高解析度計數器,精度可達 幾十奈秒
  • ElapsedTicks / Frequency 可得微秒級時間

等待策略:自旋 vs 阻塞

策略原理CPU 佔用精度適用場景
自旋等待 Thread.SpinWait 空轉消耗週期 極高 (100% 核心) 極高 (可控) 極短等待 (<1ms)、專用核心、即時要求
阻塞等待 釋放時間片,OS 排程喚醒 極低 受限於系統時鐘解析度 長等待、通用場景、需配合 timeBeginPeriod

自旋等待實作:純使用者模式微秒延遲


public static void SpinWaitPrecise(Stopwatch sw, long targetTicks)
{
    // targetTicks = 起始 ticks + 目標持續時間 * Frequency
    while (sw.ElapsedTicks < targetTicks)
    {
        // 每次迴圈約 10-20 週期,視 CPU 頻率動態調整
        Thread.SpinWait(10); 
    }
}

// 使用範例:精確延遲 500 微秒
var sw = Stopwatch.StartNew();
long start = sw.ElapsedTicks;
long target = start + (long)(500 * Stopwatch.Frequency / 1_000_000.0);
SpinWaitPrecise(sw, target);
Console.WriteLine($"實際延遲:{(sw.ElapsedTicks - start) * 1_000_000.0 / Stopwatch.Frequency:F1} µs");

缺點: 佔滿一核心,筆電/虛擬機/單核環境不可行。

阻塞等待提精度:timeBeginPeriod 破解 15ms 牆

Windows 多媒體計時器 API


[DllImport("winmm.dll", EntryPoint = "timeBeginPeriod")]
public static extern uint TimeBeginPeriod(uint uPeriod); // 設定最小週期 (ms)

[DllImport("winmm.dll", EntryPoint = "timeEndPeriod")]
public static extern uint TimeEndPeriod(uint uPeriod);   // 還原

// 使用
TimeBeginPeriod(1); // 要求 1ms 系統時鐘解析度
try 
{
    // 執行高精度定時邏輯
}
finally 
{
    TimeEndPeriod(1); // 必須成對呼叫,否則系統耗電/效能永久受影響
}

副作用: 系統全域影響,增加上下文切換、耗電、降低整體效能。Chrome、VS、遊戲、播放器皆常駐 1ms,桌面端已成常態。Win8+ 副作用明顯降低。

三種阻塞實作對比 (1ms 解析度下)

API無負載穩定值有負載穩定值誤差範圍備註
Thread.Sleep(1)~2ms~1ms+1ms需修正:Sleep(n-1) + 自旋補餘
WaitHandle.WaitOne(1)~1.5ms~0ms0~2msWaitOne(0) 不阻塞,類自旋
Socket.Poll(1000, SelectMode.SelectRead)~1ms~0ms0~1ms最穩定,佔用一 Socket 埠

多媒體定時器 timeSetEvent:1ms 精度的最佳平衡


[DllImport("winmm.dll")]
public static extern uint timeSetEvent(
    uint uDelay,        // 週期 (ms)
    uint uResolution,   // 解析度 (0=默認)
    TimerCallback lpTimeProc, // 回調委派
    uint dwUser,        // 使用者資料
    uint fuEvent        // 事件類型
);

[DllImport("winmm.dll")]
public static extern uint timeKillEvent(uint uTimerID);

// 回調簽章
public delegate void TimerCallback(uint uTimerID, uint uMsg, uint dwUser, uint dw1, uint dw2);

// 使用
uint timerId = timeSetEvent(1, 0, MyCallback, 0, 1); // TIME_PERIODIC = 1
// ...
timeKillEvent(timerId);

優勢: OS 級排程、1ms 穩定、無佔用 CPU、API 簡單。缺點: MSDN 標記過時,建議用 CreateTimerQueueTimer,但後者精度/穩定度較差,高精度場景仍首選 timeSetEvent。

完整高精度定時器架構設計

三種觸發模式


// 1. 固定時間框架:嚴格按時間點觸發,任務超時不補償,下一幀對齊原框架
nextFrame += interval;
while (now < nextFrame) Wait();
Trigger();

// 2. 可推遲時間框架:任務超時推遲後續框架
nextFrame += interval;
if (nextFrame < now) nextFrame = now; // 推遲
while (now < nextFrame) Wait();
Trigger();

// 3. 固定等待間隔:任務結束後固定等待,不對齊絕對時間
waitStart = now;
while (now - waitStart < interval) Wait();
Trigger();

混合等待策略:精度與 CPU 的最佳平衡


public void HybridWait(Stopwatch sw, long targetTicks)
{
    long remaining = targetTicks - sw.ElapsedTicks;
    
    // 剩餘 > 2ms:阻塞等待 (釋放 CPU)
    if (remaining > 2 * TicksPerMs)
    {
        int sleepMs = (int)(remaining / TicksPerMs) - 1; // 預留 1ms 緩衝
        Thread.Sleep(sleepMs);
    }
    // 剩餘 <= 2ms:自旋等待 (精度可控)
    while (sw.ElapsedTicks < targetTicks)
    {
        Thread.SpinWait(10);
    }
}

執行緒優先級提升


var thread = new Thread(TimerLoop) 
{ 
    Priority = ThreadPriority.Highest, // 或 AboveNormal
    IsBackground = true 
};
// 進程優先級同步提升
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;

警告: 高優先級可能導致低優先級執行緒飢餓,切勿在高優先級執行緒等待低優先級資源 (鎖、UI 訊息等)。

工程化檢查清單

項目必做事項
執行緒安全所有公開成員加鎖或用 Interlocked/Concurrent 集合
資源釋放實作 IDisposable,釋放 Timer、Thread、WaitHandle、Socket、timeKillEvent
例外處理回調內部 try-catch,避免摧毀定時器執行緒
取消機制提供 CancellationToken 或 volatile bool 停止旗標
精度驗證用 ClockRes (Sysinternals) 確認系統解析度

驗證工具:ClockRes 與 powercfg


// 查看目前系統時鐘解析度
> ClockRes.exe
Maximum timer interval: 15.625 ms
Minimum timer interval: 0.500 ms
Current timer interval: 1.000 ms  <-- 成功被提升至 1ms

// 查詢哪些程序要求高解析度
> powercfg /energy /duration 5
// 產生 energy-report.html,搜尋 "Platform Timer Resolution:Outstanding Timer Request"

總結

高精度定時器 = Stopwatch 計時 + timeBeginPeriod(1) 提升系統解析度 + 混合阻塞/自旋等待 + 獨立高優先級執行緒。
timeSetEvent 是 1ms 級最省力方案;自製混合循環 可達微秒級但佔用核心。
鐵律: 系統非即時,任何方案都可能失準,業務邏輯不可 完全依賴定時器觸發時間,必須設計「失去同步時的補償/檢測機制」。

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