在多執行緒應用中,常見需求是**定期執行某項工作**,例如:定時掃描資料夾、定時發送心跳封包、定時清理快取、輪詢資料庫狀態。若手動建立執行緒配合 Thread.Sleep 迴圈,不僅程式碼冗長,且難以精確控制間隔、暫停、繼續或動態調整頻率。.NET 提供的 System.Threading.Timer 類別,專為這類**背景週期性工作**設計:它使用**執行緒池** 執行回呼、支援動態變更間隔、無需手動管理執行緒生命週期。本文將完整解析 Timer 的建構函式、Change 方法、Dispose 模式,並提供 MSDN 經典範例的現代化重寫版本。
void Callback(object state),需自行處理執行緒安全。Change(Timeout.Infinite, ...) 暫停。public Timer(TimerCallback callback, object state, int dueTime, int period);
| 參數 | 型別 | 說明 |
|---|---|---|
| callback | TimerCallback | 委派,指向要週期性執行的方法 void Method(object state) |
| state | object | 傳遞給回呼的自訂狀態物件,可為 null |
| dueTime | int (毫秒) | **首次延遲**多久觸發回呼。0 = 立即執行;Timeout.Infinite = 永不啟動 |
| period | int (毫秒) | **週期間隔**。Timeout.Infinite = 僅執行一次 (一次性計時器) |
| 方法 | 用途 |
|---|---|
Change(int dueTime, int period) | 動態修改啟動延遲與週期間隔。傳入 Timeout.Infinite 可停用定期觸發。 |
Dispose() | 釋放 Timer 資源,**會等待正在執行的回呼完成** (同步等待)。配合 Dispose(WaitHandle) 可非同步等待。 |
此範例改寫自 MSDN 經典範例,展示:
using System;
using System.Threading;
namespace TimerDemo
{
///
/// 封裝 Timer 狀態的類別,避免使用 static 欄位
///
class TimerState
{
public int Counter { get; set; } = 0;
public Timer TimerRef { get; set; }
}
class Program
{
static void Main()
{
Console.WriteLine("=== System.Threading.Timer 示範 ===");
var state = new TimerState();
// 建立 TimerCallback 委派指向靜態回呼方法
TimerCallback callback = CheckStatus;
// 建立 Timer:1秒後首次觸發,之後每1秒觸發一次
Timer timer = new Timer(callback, state, 1000, 1000);
state.TimerRef = timer; // 讓回呼能存取 Timer 實例以呼叫 Change/Dispose
Console.WriteLine("主執行緒等待 Timer 結束 (按任意鍵可強制結束)...");
// 主執行緒等待:輪詢 TimerRef 是否為 null (Dispose 後設為 null)
// 實務上建議使用 ManualResetEventSlim 或 TaskCompletionSource 等待
while (state.TimerRef != null)
{
Thread.Sleep(100);
if (Console.KeyAvailable) break; // 允許手動中斷
}
Console.WriteLine("Timer 範例結束。");
Console.ReadKey();
}
///
/// Timer 回呼方法 (在 ThreadPool 執行緒執行)
///
static void CheckStatus(object stateObj)
{
var state = (TimerState)stateObj;
state.Counter++;
Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] 第 {state.Counter} 次檢查");
// 第 5 次:動態調整間隔
if (state.Counter == 5)
{
Console.WriteLine(" → 變更間隔:10秒後重新開始,之後每2秒觸發一次");
// dueTime=10000 (10秒後首次),period=2000 (每2秒)
state.TimerRef.Change(10000, 2000);
}
// 第 10 次:停止並釋放 Timer
if (state.Counter == 10)
{
Console.WriteLine(" → 計數達 10,釋放 Timer 並結束");
state.TimerRef.Dispose(); // 同步等待回呼完成
state.TimerRef = null; // 通知主執行緒迴圈結束
}
}
}
}
[14:30:00.000] 第 1 次檢查
[14:30:01.000] 第 2 次檢查
[14:30:02.000] 第 3 次檢查
[14:30:03.000] 第 4 次檢查
[14:30:04.000] 第 5 次檢查
→ 變更間隔:10秒後重新開始,之後每2秒觸發一次
[14:30:14.000] 第 6 次檢查 (10秒後)
[14:30:16.000] 第 7 次檢查
[14:30:18.000] 第 8 次檢查
[14:30:20.000] 第 9 次檢查
[14:30:22.000] 第 10 次檢查
→ 計數達 10,釋放 Timer 並結束
Timer 範例結束。
// 5 秒後執行一次,不重複
Timer oneShot = new Timer(callback, state, 5000, Timeout.Infinite);
// dueTime=0 立即觸發第一次
Timer immediate = new Timer(callback, state, 0, 1000);
若回呼執行時間可能超過間隔,必須加鎖:
private readonly object _lock = new object();
void Callback(object state)
{
if (!Monitor.TryEnter(_lock)) return; // 上次未完成,直接跳過
try
{
// 實際工作...
}
finally { Monitor.Exit(_lock); }
}
或使用 Change(Timeout.Infinite, Timeout.Infinite) 暫停,工作完成後再 Change(0, period) 恢復。
Dispose():同步等待目前正在執行的回呼完成,**會阻塞呼叫執行緒**。Dispose(WaitHandle notifyObject):非同步釋放,回呼完成後會 Set() 指定的 WaitHandle (如 ManualResetEvent),適合 UI 執行緒避免凍結。| 類別 | 命名空間 | 執行緒模型 | 適用場景 | UI 安全 |
|---|---|---|---|---|
System.Threading.Timer | System.Threading | ThreadPool 背景執行緒 | 伺服器端背景作業、高頻率、無 UI 互動 | 否 (回呼非 UI 執行緒) |
System.Timers.Timer | System.Timers | ThreadPool (元件模型) | 元件化設計、支援 SynchronizingObject 導向 UI 執行緒 | 是 (設定 SynchronizingObject) |
System.Windows.Forms.Timer | System.Windows.Forms | UI 執行緒 (訊息迴圈) | WinForms UI 更新、低頻率 (>50ms) | 是 (原生 UI 執行緒) |
System.Windows.Threading.DispatcherTimer | System.Windows.Threading | UI 執行緒 (Dispatcher) | WPF UI 更新 | 是 |
System.Threading.Timer 是 .NET 核心庫中最輕量、效能最好的**背景週期性工作**排程器。它完全由執行緒池驅動,無需手動管理執行緒,支援動態調整間隔 (Change) 與正確資源釋放 (Dispose)。掌握其「非同步回呼、可重入風險、IDisposable 模式」三大核心特性,即可在伺服器端、Windows Service、主控台應用中穩健實作定時掃描、輪詢、心跳、清理等常見需求。若涉及 UI 更新,請改用 System.Timers.Timer (配合 SynchronizingObject) 或 UI 框架專用 Timer。