| 類別 | 命名空間 | 底層機制 | 精度 | 適用場景 | 缺點 |
|---|---|---|---|---|---|
System.Windows.Forms.Timer |
System.Windows.Forms | Windows 訊息迴圈 (WM_TIMER) | 低 (~15-55ms) | WinForms 簡單 UI 定時更新 | 需 UI 執行緒、無法用於 Console/Service、精度差 |
System.Timers.Timer |
System.Timers | ThreadPool (I/O 完成通知) | 中 (~10-15ms) | Server 端定時任務、WinForms/WPF (需手動 Marshal) | 事件在 ThreadPool 執行、需自行同步 UI |
System.Threading.Timer |
System.Threading | ThreadPool (輕量級) | 中 (~10-15ms) | 高頻背景工作、無 UI 環境、ASP.NET Core | 無事件模型、需手動處理重入、同步 |
AutoReset、Enabled、Elapsed 事件,適合拖拉式設計(雖然實際不支援設計工具拖拉)。System.Timers.Timer 內部封裝 System.Threading.Timer,額外提供 SynchronizingObject 自動封送至 UI 執行緒。
using System.Timers;
var timer = new Timer(10000) // 10 秒間隔
{
AutoReset = true, // true=週期,false=單次
Enabled = true // 啟動
};
timer.Elapsed += (sender, e) =>
{
Console.WriteLine($"觸發時間:{e.SignalTime:HH:mm:ss.fff}");
// 注意:此處在 ThreadPool 執行緒,若操作 UI 必須 Invoke
};
Console.ReadLine(); // 保持程式運行
timer.Dispose(); // 釋放資源
using System.Threading;
Timer timer = null;
timer = new Timer(
callback: state =>
{
Console.WriteLine($"回調執行:{DateTime.Now:HH:mm:ss.fff}");
// 單次執行後自行銷毀
timer?.Dispose();
},
state: null, // 傳遞給回調的狀態物件
dueTime: 0, // 立即啟動
period: 2000 // 每 2 秒週期 (Timeout.Infinite 為單次)
);
Console.ReadLine();
class Program
{
static void Main()
{
var timer = new Timer(_ => Console.WriteLine("Tick"), null, 0, 2000);
Console.ReadLine(); // Debug 下正常,Release 下只印一次!
}
}
| 模式 | IL 關鍵差異 | 結果 |
|---|---|---|
| Debug | stloc.0 將 timer 存入區域變數槽 0 |
JIT 延長區域變數壽命至方法結束,GC 看見引用不回收 |
| Release | pop 直接丟棄 newobj 回傳參考 |
timer 變數無實際引用,GC 判定可回收,下次掃描即銷毀 |
核心原則: Timer 內部不持有對自身的強引用,若外部變數在優化後不再被讀取,GC 視為垃圾。
// 1. 類別層級欄位 (最推薦,生命週期清晰)
private static Timer _timer;
// 2. 使用 GC.KeepAlive (明確告知 JIT 別優化掉)
var timer = new Timer(...);
Console.ReadLine();
GC.KeepAlive(timer); // 放在方法末尾
// 3. 存入靜態集合 (適用動態建立大量 Timer)
static readonly List _timers = new();
_timers.Add(timer);
// 移除時 _timers.Remove(timer); timer.Dispose();
// 4. 實作 IDisposable 容器統一管理
public class TimerManager : IDisposable
{
private readonly List _timers = new();
public Timer Create(...) { var t = new Timer(...); _timers.Add(t); return t; }
public void Dispose() => _timers.ForEach(t => t.Dispose());
}
var timer = new Timer(1000) { AutoReset = true };
bool _running = false;
timer.Elapsed += (s, e) =>
{
// 雙重檢查鎖防重入
if (Interlocked.Exchange(ref _running, 1) == 1) return;
try
{
DoWork(); // 耗時操作
}
finally
{
Volatile.Write(ref _running, 0);
}
};
public async Task StartPeriodicAsync(Action work, TimeSpan interval, CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
var sw = Stopwatch.StartNew();
work(); // 或 await workAsync()
sw.Stop();
var delay = interval - sw.Elapsed;
if (delay > TimeSpan.Zero)
await Task.Delay(delay, ct);
}
}
// 使用
var cts = new CancellationTokenSource();
await StartPeriodicAsync(() => Console.WriteLine("Tick"), TimeSpan.FromSeconds(2), cts.Token);
// 隨時可取消:cts.Cancel();
優勢: 自然支援非同步、取消、例外傳播、無 GC 陷阱、易於單元測試。
| 場景 | 推薦方案 |
|---|---|
| WinForms/WPF 簡單 UI 定時 | System.Windows.Forms.Timer / DispatcherTimer |
| 後端服務定時作業 | System.Threading.Timer 或 IHostedService + Task.Delay |
| 需自動 UI 封送的跨平台 | System.Timers.Timer (設定 SynchronizingObject) |
| 高精度、複雜排程 | Quartz.NET / Hangfire / Cronos |
| 現代 .NET 非同步週期任務 | Task.Delay 循環 + CancellationToken |
記住:Timer 必須被強引用持有,否則 Release 模式下會神秘消失。優先考慮 Task.Delay 循環,它更安全、更現代、更符合非同步程式設計範式。