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

C# 多線程進階:線程池、Timer 與異步編程模式完整指南

     編輯:關於C語言

何時選擇線程池?

  • ✅ 大量短暫任務(如 Web 伺服器請求處理、並行運算分塊)。
  • ✅ 不需精細控制線程屬性(優先級、名稱、前景/背景)。
  • ✅ 任務間相對獨立,無複雜同步需求。
  • ❌ 長時間運行任務(會佔用池線程導致飢餓)。
  • ❌ 需單執行緒單元 (STA) 或特定線程身份。
  • ❌ 需精確控制線程生命週期(掛起/恢復/中止)。

ThreadPool 核心 API

佇列工作項目

// 簡單火燒忘
ThreadPool.QueueUserWorkItem(state => 
{
    DoWork(state);
}, payload);

// 等待可等待控制代碼觸發
ThreadPool.RegisterWaitForSingleObject(
    waitHandle,           // ManualResetEvent, AutoResetEvent 等
    (state, timedOut) => Callback(state), 
    state, 
    timeoutMs, 
    executeOnlyOnce: true);

關鍵特性

特性說明
自動調節根據 CPU 核心數、工作負載動態增減工作者線程(預設每核心最多 25 線程,可調 SetMaxThreads/SetMinThreads)。
背景線程池線程皆為背景線程,不阻礙程序退出。
無法取消一旦佇列,無法撤回;需自行實作取消令牌模式。
堆疊大小固定預設 1MB,不可調;深度遞迴謹慎。
安全性檢查預設流動呼叫堆疊進行 CAS 檢查;高效能場景可用 UnsafeQueueUserWorkItem 跳過(需極高信任)。

System.Threading.Timer:高精度週期性任務

// 建構:回調、狀態、首次延遲、週期間隔(毫秒)
var timer = new Timer(
    callback: state => DoPeriodicWork(),
    state: null,
    dueTime: TimeSpan.FromSeconds(5),   // 5 秒後首次執行
    period: TimeSpan.FromMinutes(10));  // 之後每 10 分鐘執行

// 動態調整
timer.Change(TimeSpan.FromSeconds(10), TimeSpan.FromMinutes(5));

// 釋放資源(必須調用,否則回調持續觸發)
timer.Dispose();

適用場景

  • 控制台應用/Windows Service 定時作業(無 UI 執行緒)。
  • 高頻輪詢(如檢查檔案變更、心跳檢測)。
  • 非 UI 執行緒環境下的定時器(System.Windows.Forms.Timer 需 UI 訊息泵)。

異步編程模型 (APM / EAP / TAP) 演進

歷史回顧

  1. APM (Begin/End):BeginInvoke/EndInvoke、IAsyncResult。難以組合、錯誤處理分散、回調地獄。
  2. EAP (Event-based):XxxAsync + XxxCompleted 事件。適合 UI 繫結,但同樣難組合。
  3. TAP (Task-based):Task/Task<T> + async/await。現代標準,支援組合、異常冒泡、取消、進度報告、同步上下文捕獲。

為什麼異步能提升擴展性?

同步阻塞:線程等待 I/O 完成 → 線程閒置浪費。
異步非阻塞:發起 I/O → 線程歸還池處理其他請求 → I/O 完成由完成埠通知 → 線程池線程接續後續邏輯。
同樣線程數可服務更多並發請求,關鍵指標:吞吐量、延遲、資源利用率。

現代最佳實踐

// I/O 密集型:使用 async/await,不要 Task.Run 包裝同步方法
public async Task DownloadAsync(string url, CancellationToken ct = default)
{
    using var client = new HttpClient();
    return await client.GetStringAsync(url, ct);
}

// CPU 密集型:確需卸載至背景線程才用 Task.Run
public Task ComputeHeavyAsync(int[] data) => Task.Run(() => HeavyCompute(data));

// 並行控制:SemaphoreSlim 限制併發度
var semaphore = new SemaphoreSlim(10);
await semaphore.WaitAsync();
try { await DoLimitedWork(); }
finally { semaphore.Release(); }

線程池 vs Task.Run vs 自建線程 選擇矩陣

場景推薦方式理由
短時 I/O 任務async/await + 內建異步 API無線程佔用,最高擴展性
短時 CPU 任務Task.Run利用線程池,自動負載平衡
長時運行/獨占資源自建 Thread (IsBackground=true)不污染線程池,可精確控制
週期性任務Timer / PeriodicTimer (.NET 6+)高精度、低開銷
需 STA/線程親和性自建 Thread + SetApartmentState線程池皆為 MTA

常見陷阱與避坑

  • 🚫 同步等待異步:.Result/.Wait() 導致死鎖 (UI/ASP.NET 同步上下文) 或線程池飢餓。全鏈路 async/await。
  • 🚫 在線程池執行長任務:耗盡工作者線程,導致 Timer/異步完成無線程可用。長任務請自建線程或限制併發度。
  • 🚫 忘記 Dispose Timer:導致記憶體洩漏與無限回調。
  • 🚫 捕獲迴圈變數:for (int i=0; i<10; i++) Task.Run(() => Use(i)) → 全部見到最終值 10。修正:int local = i; 在迴圈內部。
  • 🚫 異常吞沒:Task.Run 未觀察 Task.Exception。使用 await 或 ContinueWith(t => Log(t.Exception), TaskContinuationOptions.OnlyOnFaulted)。

總結

線程池與 Timer 是 .NET 並行基礎設施的核心組件,配合現代 async/await (TAP) 模式,能構建高擴展、響應迅速的應用程式。核心原則:I/O 用異步,CPU 用 Task.Run,長任務自建線程,週期用 Timer,永遠觀察異常,謹慎同步等待。

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