無論客戶端應用還是伺服器組件,計時器都是關鍵角色。編寫高效的計時器驅動代碼,需要清晰理解程序流程與 .NET 線程模型。.NET Framework 提供三種計時器:
System.Windows.Forms.Timer(UI 線程)System.Timers.Timer(伺服器/工作者線程)System.Threading.Timer(高階/線程池)前兩者出現在 VS 工具箱,最後一個僅程式碼使用。選錯類別將導致嚴重效能或正確性問題。
特性:事件在 UI 線程同步引發,不會搶佔 正在執行的代碼(除非呼叫 Application.DoEvents)。UI 線程同時負責處理視窗訊息佇列。
間隔 1000ms,點擊 Start → Sleep(5秒) → Stop,輸出:
Timer Event 1 @ 4:09:29 on Thread: UIThread
Timer Event 2 @ 4:09:30 on Thread: UIThread
Timer Event 3 @ 4:09:31 on Thread: UIThread
Sleeping for 5000 ms...
Timer Event 4 @ 4:09:36 on Thread: UIThread <-- 睡眠期間事件丟失,無排隊
UI 線程睡眠時計時器掛起,喚醒後不補發遺漏事件。
var tmr = new System.Windows.Forms.Timer();
tmr.Interval = 1000; // 約 55ms 精度
tmr.Tick += (s,e) => { /* UI 線程執行 */ };
tmr.Start();
特性:預設在 CLR 線程池工作者線程 引發 Elapsed 事件,支援多線程安全訪問。提供 SynchronizingObject 屬性強制事件在指定物件建立線程(如 UI 線程)執行。
相同測試,輸出:
Timer Event 4 @ 4:09:36 on Thread: WorkerThread
Timer Event 5 @ 4:09:37 on Thread: WorkerThread
...
Timer Event 8 @ 4:09:40 on Thread: WorkerThread <-- 睡眠期間事件排隊,喚醒後批次處理
不會跳過任何事件,即使 UI 線程忙碌。
AutoReset:是否重複觸發Interval 修改會重置計數器ElapsedEventArgs.SignalTime:精確觸發時間,配合 Stop() 判斷競態tmr.SynchronizingObject = this; // 表單或控件
// 內部使用 SynchronizingObject.BeginInvoke 排程
特性:不在工具箱,介面較複雜,非天生線程安全(但可安全使用)。四個建構函數重載:
public Timer(TimerCallback callback, object state, long dueTime, long period);
// dueTime: 首次觸發延遲 (0=立即, Timeout.Infinite=不啟動)
// period: 間隔 (0/Timeout.Infinite=單次)
TimerCallback 簽名:void TimerCallback(object state),在工作者線程執行,需自行保證 state 線程安全。
tmr.Change(0, 1000); // 啟動
tmr.Change(Timeout.Infinite, Timeout.Infinite); // 停止
行為同 System.Timers.Timer:工作者線程觸發,事件排隊不丟失。
control.Invoke((Action)(() => { /* UI 代碼 */ }));
private delegate void ShowTimerEventFiredDelegate(DateTime time, string threadName);
private void ShowTimerEventFired(DateTime time, string threadName)
{
if (listBox.InvokeRequired)
listBox.BeginInvoke(new ShowTimerEventFiredDelegate(ShowTimerEventFired), time, threadName);
else
listBox.Items.Add($"{time} on {threadName}");
}
若事件處理耗時超過間隔,會並發執行導致狀態損壞。解決方案:
private void tmr_Elapsed(object sender, ElapsedEventArgs e)
{
tmr.Enabled = false; // 暫停
try {
Interlocked.Increment(ref counter);
Thread.Sleep(5000);
MessageBox.Show(counter.ToString());
} finally {
tmr.Enabled = true; // 恢復
}
}
或使用 Monitor/Mutex 排隊。
| 特性 | Forms.Timer | Timers.Timer | Threading.Timer |
|---|---|---|---|
| 觸發線程 | UI 線程 | 工作者線程 | 工作者線程 |
| 事件排隊 | 否(丟失) | 是 | 是 |
| UI 同步支援 | 原生 | SynchronizingObject | 手動 Invoke |
| 精度 | ~55ms | 高 | 高 |
| 適用場景 | 簡單 UI 動畫 | 伺服器/需同步 UI | 高效能/輕量/無 UI |
選型建議:能用 Windows 排程器執行獨立程式解決問題時,優先考慮更簡單的方案。