前言
在高併發、高吞吐量的應用場景中(如 Web 伺服器、批次作業處理、大量 I/O 操作),頻繁建立與銷毀執行緒會造成嚴重的**效能開銷**(記憶體配置、核心物件建立、上下文切換)。.NET 提供的 ThreadPool (執行緒池) 解決了這個問題:它維護一組**可重複使用的背景執行緒**,自動管理執行緒的建立、回收、排程與負載平衡。開發者只需將工作項 (Work Item) 佇列到執行緒池,其餘交給執行時自動處理。本文將深入解析 ThreadPool.QueueUserWorkItem、WaitCallback 委派、ManualResetEvent 同步等待、Interlocked 原子操作等核心技術,並提供完整的 MSDN 經典範例現代化重寫版本。
何時使用 ThreadPool?
| 場景 | 說明 |
| 短暫、獨立的背景工作 | 如:發送 Email、寫入日誌、壓縮檔案、呼叫 Web API |
| 大量同類工作佇列 | 如:平行處理 10,000 筆訂單、批次匯入資料 |
| I/O 密集型非同步操作 | 配合 Task.Run 或 ThreadPool.QueueUserWorkItem 釋放呼叫執行緒 |
| 不需精細控制執行緒生命週期 | 不需設定優先級、IsBackground、名稱、單一實例等 |
核心 API:QueueUserWorkItem
// 基本版:無狀態物件
public static bool QueueUserWorkItem(WaitCallback callBack);
// 進階版:傳遞自訂狀態物件
public static bool QueueUserWorkItem(WaitCallback callBack, object state);
WaitCallback:委派簽名 void Method(object state),回呼在執行緒池執行緒執行。
state:任意物件,用於傳遞工作所需資料 (如 ID、連線字串、檔案路徑)。
- 回傳
bool:成功佇列回傳 true;若執行緒池已關閉或資源耗盡回傳 false。
- 不可取消:一旦佇列成功,**無法取消**該工作項 (需自行實作取消邏輯,如
CancellationToken)。
同步等待機制:ManualResetEvent
主執行緒常需等待所有佇列工作完成。ManualResetEvent 如同**手動號誌燈**:
new ManualResetEvent(false):初始為**紅燈 (無號誌/Non-signaled)**。
Set():轉為**綠燈 (有號誌/Signaled)**,**持續保持綠燈**,直到呼叫 Reset()。
WaitOne():阻塞目前執行緒,直到號誌為綠燈。
Reset():手動轉回紅燈。
與 AutoResetEvent (自動重設、一次放行一個) 不同,ManualResetEvent 適合**一對多**通知:一個 Set() 釋放所有等待的執行緒。
原子操作:Interlocked 類別
多執行緒修改共用變數時,Interlocked 提供**無鎖原子操作**,效能優於 lock:
| 方法 | 作用 | 範例 |
Increment(ref int) | 原子遞增並回傳新值 | Interlocked.Increment(ref counter) |
Decrement(ref int) | 原子遞減並回傳新值 | Interlocked.Decrement(ref counter) |
Add(ref int, int) | 原子加法 | Interlocked.Add(ref total, 100) |
Exchange(ref T, T) | 原子交換並回傳舊值 | Interlocked.Exchange(ref ptr, newObj) |
CompareExchange(ref T, T, T) | 原子比較交換 (CAS) | 無鎖資料結構基石 |
完整範例:佇列 10 項工作、統計各執行緒負載、等待全部完成
此範例改寫自 MSDN 經典範例,展示:
- 建立
ManualResetEvent 等待所有工作完成
- 佇列 10 個工作項,每項攜帶
SomeState (含 Cookie ID)
- 工作項
Beta:記錄執行緒 HashCode、模擬工作 (Sleep 2秒)、Interlocked.Increment 計數
- 最後一項完成時
eventX.Set() 通知主執行緒
- 主執行緒列印各執行緒處理項目數 (負載分布)
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
namespace ThreadPoolDemo
{
///
/// 工作項狀態物件,攜帶 Cookie ID
///
class WorkItemState
{
public int Cookie { get; }
public WorkItemState(int cookie) => Cookie = cookie;
}
///
/// 協調器:持有 HashTable 統計、ManualResetEvent 同步
///
class Coordinator
{
public Hashtable ThreadLoad { get; } = new Hashtable();
public ManualResetEvent CompletionEvent { get; } = new ManualResetEvent(false);
public int CompletedCount { get; private set; } = 0;
public int TotalWorkItems { get; }
public Coordinator(int totalWorkItems)
{
TotalWorkItems = totalWorkItems;
}
///
/// 工作項回呼 (在 ThreadPool 執行緒執行)
///
public void ProcessWorkItem(object stateObj)
{
var state = (WorkItemState)stateObj;
int threadHash = Thread.CurrentThread.GetHashCode();
Console.WriteLine($"[Thread {threadHash}] 開始工作 Cookie={state.Cookie}");
// 線程安全地記錄負載
lock (ThreadLoad)
{
if (!ThreadLoad.ContainsKey(threadHash))
ThreadLoad[threadHash] = 0;
ThreadLoad[threadHash] = (int)ThreadLoad[threadHash] + 1;
}
// 模擬耗時工作 (2 秒)
Thread.Sleep(2000);
// 原子遞增完成計數
int completed = Interlocked.Increment(ref CompletedCount);
Console.WriteLine($"[Thread {threadHash}] 完成工作 Cookie={state.Cookie} (累計完成 {completed}/{TotalWorkItems})");
// 最後一項完成,通知主執行緒
if (completed == TotalWorkItems)
{
Console.WriteLine("→ 所有工作完成,發送完成信號");
CompletionEvent.Set();
}
}
}
class Program
{
static void Main()
{
const int MAX_WORK_ITEMS = 10;
Console.WriteLine($"=== ThreadPool 示範:佇列 {MAX_WORK_ITEMS} 項工作 ===");
var coordinator = new Coordinator(MAX_WORK_ITEMS);
// 佇列第一項 (測試環境是否支援 ThreadPool)
try
{
ThreadPool.QueueUserWorkItem(coordinator.ProcessWorkItem, new WorkItemState(0));
}
catch (NotSupportedException)
{
Console.WriteLine("目前平台不支援 ThreadPool API (需 Windows 2000 以上)。");
Console.ReadKey();
return;
}
// 佇列其餘 9 項
for (int i = 1; i < MAX_WORK_ITEMS; i++)
{
ThreadPool.QueueUserWorkItem(coordinator.ProcessWorkItem, new WorkItemState(i));
}
Console.WriteLine("主執行緒等待所有工作完成...");
// 封鎖主執行緒,直到 CompletionEvent.Set() 被呼叫
// Timeout.Infinite = 無限期等待
coordinator.CompletionEvent.WaitOne(Timeout.Infinite);
Console.WriteLine("\n=== 執行緒負載分布 ===");
foreach (DictionaryEntry entry in coordinator.ThreadLoad)
{
Console.WriteLine($"Thread Hash: {entry.Key,-10} 處理項目數: {entry.Value}");
}
Console.WriteLine("\n按任意鍵結束...");
Console.ReadKey();
}
}
}
程式執行輸出範例
=== ThreadPool 示範:佇列 10 項工作 ===
[Thread 4] 開始工作 Cookie=0
[Thread 5] 開始工作 Cookie=1
[Thread 6] 開始工作 Cookie=2
[Thread 7] 開始工作 Cookie=3
[Thread 8] 開始工作 Cookie=4
[Thread 9] 開始工作 Cookie=5
[Thread 10] 開始工作 Cookie=6
[Thread 11] 開始工作 Cookie=7
[Thread 12] 開始工作 Cookie=8
[Thread 13] 開始工作 Cookie=9
主執行緒等待所有工作完成...
[Thread 4] 完成工作 Cookie=0 (累計完成 1/10)
[Thread 5] 完成工作 Cookie=1 (累計完成 2/10)
...
[Thread 13] 完成工作 Cookie=9 (累計完成 10/10)
→ 所有工作完成,發送完成信號
=== 執行緒負載分布 ===
Thread Hash: 4 處理項目數: 2
Thread Hash: 5 處理項目數: 1
Thread Hash: 6 處理項目數: 1
Thread Hash: 7 處理項目數: 1
Thread Hash: 8 處理項目數: 1
Thread Hash: 9 處理項目數: 1
Thread Hash: 10 處理項目數: 1
Thread Hash: 11 處理項目數: 1
Thread Hash: 12 處理項目數: 1
Thread Hash: 13 處理項目數: 1
進階主題與最佳實踐
1. 現代替代方案:Task.Run / TaskFactory
.NET 4.0+ 強烈建議使用 Task.Run(() => DoWork()) 或 Task.Factory.StartNew:
- 回傳
Task 物件,支援 await、ContinueWith、Task.WhenAll 等組合模式。
- 預設同樣使用 ThreadPool,但提供更豐富的非同步程式設計模型。
- 範例:
var tasks = new List();
for (int i = 0; i < 10; i++)
{
int cookie = i;
tasks.Add(Task.Run(() => ProcessWorkItem(cookie)));
}
await Task.WhenAll(tasks); // 非同步等待全部完成,不阻塞主執行緒
2. 執行緒池參數調整
ThreadPool.SetMinThreads(int workerThreads, int completionPortThreads):設定**最小閒置執行緒數**,避免突發負載時延遲建立執行緒 (預設較保守)。
ThreadPool.SetMaxThreads(int workerThreads, int completionPortThreads):設定**最大執行緒數**,防止資源耗盡。
ThreadPool.GetAvailableThreads(out worker, out io):取得目前可用執行緒數。
3. 避免在 ThreadPool 執行緒執行長時間阻塞工作
ThreadPool 執行緒數有限,若大量工作項執行 Thread.Sleep、同步 I/O、資料庫同步查詢,會導致**執行緒池耗盡**,新工作項無法執行。解決方案:
- 改用非同步 I/O (
async/await、HttpClient、SqlCommand.ExecuteReaderAsync) 釋放執行緒。
- 長時間 CPU 密集工作改用
Task.Run 配合 TaskCreationOptions.LongRunning (建立專用執行緒,不佔用池)。
4. 例外處理
ThreadPool 工作項拋出的未捕獲例外會**導致整個行程終止** (.NET 2.0+)。務必在回呼內部 try-catch 處理所有例外,或使用 Task 的例外觀察機制。
ThreadPool vs Thread vs Task 比較
| 特性 | Thread | ThreadPool (QueueUserWorkItem) | Task (Task.Run) |
| 建立/銷毀開銷 | 高 (核心物件) | 低 (池化重用) | 低 (池化重用) |
| 控制精細度 | 最高 (優先級、名稱、IsBackground) | 低 (無法控制) | 中 (可配置 LongRunning、Scheduler) |
| 回傳值/續作 | 困難 | 困難 (需自行實作) | 原生支援 (Task<T>、await、WhenAll) |
| 取消支援 | 手動旗標 | 手動旗標 | 原生 CancellationToken |
| 例外觀察 | 手動 | 手動 (未捕獲殺進程) | 自動 (Task.Exception、await 拋出) |
| 適用場景 | 長運行、需精細控制、單一實例 | 舊版相容、簡單火燒忘 | 現代非同步程式設計首選 |
總結
ThreadPool 是 .NET 執行緒管理的基石,所有非同步 I/O、Task.Run、Parallel.ForEach、ASP.NET Core 請求處理底層皆仰賴它。掌握 QueueUserWorkItem、WaitCallback、ManualResetEvent 同步、Interlocked 原子操作等核心技術,對於維護舊版專案、理解底層運作機制至關重要。現代開發 (.NET 4.0+) **首選 Task.Run / async/await**,享受更豐富的組合性、取消、例外處理與除錯體驗。無論選用何種 API,**避免在池執行緒阻塞**、**正確處理例外**、**設定適當的 Min/Max 執行緒數** 是高效能、穩定應用的關鍵。
- 上一篇文章: C# 多執行緒自動管理:Timer 定時器完整教學與範例
- 下一篇文章: C# 多執行緒同步化:lock 與 Monitor 實作生產者消費者模式完整教學
Copyright ©
程式碼之家 All Rights Reserved