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

.NET 三大計時器完整比較:System.Windows.Forms.Timer vs System.Timers.Timer vs System.Threading.Timer

     編輯:關於C#

核心差異一覽表

特性WinForms TimerTimers.TimerThreading.Timer
命名空間System.Windows.FormsSystem.TimersSystem.Threading
執行緒模型UI 執行緒 (單執行緒)執行緒池 (多執行緒)執行緒池 (多執行緒)
觸發事件執行緒UI 執行緒 (安全操作控制項)執行緒池執行緒 (需 Marshal 回 UI)執行緒池執行緒 (需 Marshal 回 UI)
精確度低 (~55ms 解析度)中高 (系統計時器)高 (系統計時器)
適用場景UI 動畫、定時刷新介面伺服器端定時任務、需同步 UI 時高頻背景任務、無 UI 需求
IDisposable是是是
Stop/Start 方法Stop()/Start()Stop()/Start()Change(Timeout.Infinite, ...)/Change(...)
自動重置是 (Interval 週期)AutoReset 屬性控制週期參數控制

1. WinForms Timer (UI 執行緒安全)


public partial class MainForm : Form
{
    private System.Windows.Forms.Timer _uiTimer;

    public MainForm()
    {
        InitializeComponent();
        _uiTimer = new System.Windows.Forms.Timer { Interval = 2000 };
        _uiTimer.Tick += UiTimer_Tick;
    }

    private void btnStart_Click(object sender, EventArgs e)
    {
        _uiTimer.Start();
    }

    private void UiTimer_Tick(object sender, EventArgs e)
    {
        // 直接安全操作 UI 控制項
        lblTime.Text = DateTime.Now.ToString("HH:mm:ss.fff");
        RefreshDataGrid(); // 直接繫結資料
    }

    private void RefreshDataGrid()
    {
        // 同步資料庫查詢 (若耗時長會凍結 UI,建議改非同步)
        using (var conn = new SqlConnection(connStr))
        using (var da = new SqlDataAdapter("SELECT * FROM Logs ORDER BY Id DESC", conn))
        {
            var dt = new DataTable();
            da.Fill(dt);
            dataGridView1.DataSource = dt;
        }
    }
}
⚠️ Tick 事件在 UI 執行緒執行,**嚴禁執行耗時操作** (資料庫查詢、網路請求、大量運算),否則介面會卡死。耗時工作請用 Task.Run 配合 await。

2. Timers.Timer (伺服器端/需同步 UI)


public partial class MainForm : Form
{
    private System.Timers.Timer _serverTimer;

    public MainForm()
    {
        InitializeComponent();
        _serverTimer = new System.Timers.Timer(2000);
        _serverTimer.Elapsed += ServerTimer_Elapsed;
        _serverTimer.SynchronizingObject = this; // 關鍵:自動 Marshal 回 UI 執行緒
    }

    private void ServerTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {
        // 此處已在 UI 執行緒 (因 SynchronizingObject)
        // 但建議仍用非同步避免阻塞計時器執行緒
        _ = RefreshDataAsync();
    }

    private async Task RefreshDataAsync()
    {
        try
        {
            using (var conn = new SqlConnection(connStr))
            await conn.OpenAsync();
            using (var cmd = new SqlCommand("SELECT * FROM Logs ORDER BY Id DESC", conn))
            using (var reader = await cmd.ExecuteReaderAsync())
            {
                var dt = new DataTable();
                dt.Load(reader);
                // UI 繫結需在 UI 執行緒 (SynchronizingObject 已保證)
                dataGridView1.DataSource = dt;
            }
        }
        catch (Exception ex)
        {
            // 錯誤處理
        }
    }
}

SynchronizingObject = this 讓 Elapsed 事件自動透過 Control.Invoke 回 UI 執行緒,免寫手動 Invoke 程式碼。

3. Threading.Timer (輕量背景計時器)


public class BackgroundService : IDisposable
{
    private System.Threading.Timer _timer;
    private readonly Action _work;

    public BackgroundService(Action work, TimeSpan dueTime, TimeSpan period)
    {
        _work = work;
        // dueTime: 首次延遲, period: 週期 (Timeout.InfiniteTimeSpan 為單次)
        _timer = new System.Threading.Timer(_ => ExecuteWork(), null, dueTime, period);
    }

    private void ExecuteWork()
    {
        try
        {
            _work(); // 在執行緒池執行緒執行
        }
        catch (Exception ex)
        {
            // 記錄日誌,避免未觀測例外終結進程
        }
    }

    public void Dispose() => _timer?.Dispose();

    // 動態變更週期
    public void ChangePeriod(TimeSpan newPeriod) => _timer.Change(TimeSpan.Zero, newPeriod);
}

// 使用
var service = new BackgroundService(() => 
{
    Console.WriteLine($"背景工作執行:{DateTime.Now}");
    // 發送心跳、清理快取、同步資料...
}, TimeSpan.Zero, TimeSpan.FromMinutes(5));

進階:非同步輪詢模式 (現代最佳實踐)


// 取代所有 Timer:使用 Task.Delay 迴圈,完整支援 async/await、取消權杖、例外處理
public class AsyncPoller
{
    private readonly Func _action;
    private readonly TimeSpan _interval;
    private readonly CancellationTokenSource _cts = new();
    private Task _pollingTask;

    public AsyncPoller(Func action, TimeSpan interval)
    {
        _action = action;
        _interval = interval;
    }

    public void Start() => _pollingTask = PollLoopAsync(_cts.Token);

    public async Task StopAsync()
    {
        _cts.Cancel();
        if (_pollingTask != null) await _pollingTask;
    }

    private async Task PollLoopAsync(CancellationToken token)
    {
        while (!token.IsCancellationRequested)
        {
            try
            {
                await _action();
            }
            catch (Exception ex)
            {
                // 記錄錯誤,決定是否繼續
            }
            
            try
            {
                await Task.Delay(_interval, token);
            }
            catch (OperationCanceledException) { break; }
        }
    }
}

// 使用
var poller = new AsyncPoller(async () => 
{
    await RefreshDataFromApiAsync();
    UpdateUI(); // 需在 UI 執行緒
}, TimeSpan.FromSeconds(30));

poller.Start();
// 程式結束時
await poller.StopAsync();

選型決策樹

  1. **純 UI 定時刷新、動畫、簡單輪詢** → System.Windows.Forms.Timer (最簡單、最安全)
  2. **伺服器端服務、Windows Service、無 UI 背景工作** → System.Threading.Timer 或 AsyncPoller (輕量、精確)
  3. **需在計時器中操作 UI、且邏輯較複雜** → System.Timers.Timer + SynchronizingObject 或 AsyncPoller + Control.Invoke
  4. **高精度、可取消、支援 async/await** → **一律優先 AsyncPoller (Task.Delay 迴圈)**

常見陷阱避坑

  • WinForms Timer 重入:若 Tick 處理時間超過 Interval,會佇列堆疊。解決:Tick 開頭 timer.Stop(),結尾 timer.Start()。
  • Timers.Timer 事件重疊:AutoReset=true (預設) 且處理時間超過 Interval 會在執行緒池併發觸發。解決:AutoReset=false 手動重啟,或用 lock 保護共享資源。
  • Threading.Timer 未 Dispose:會導致記憶體洩漏與持續觸發。實作 IDisposable 確保釋放。
  • 捕獲變數閉包陷阱:迴圈中建立 Timer 捕獲迴圈變數,全部指向同一變數。解決:區域變數複製 var i = index;。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved