在探索 C# 多線程程式設計的過程中,線程池是一個核心且不可或缺的概念。許多開發者初次接觸時可能感到困惑,透過查閱 MSDN 文件與社群資源,本文整理出關於 ThreadPool 的核心觀念與實作範例,希望能為後續學習者提供清晰的指引。
ThreadPool 是 .NET Framework 提供的一種機制,讓應用程式能更有效地利用多線程。許多應用程式雖然使用多線程,但這些線程往往花費大量時間處於休眠狀態等待事件,且手動管理線程生命週期極為繁瑣。線程池的出現,正是為了解決這些問題——它提供了一組由系統自動管理的背景線程,讓開發者能專注於業務邏輯而非線程調度。
ThreadPool.QueueUserWorkItem 是最便捷且高效的選擇。Framework 內部大量依賴線程池處理:非同步委托調用、System.Net 套接字連接、非同步 I/O 完成通知、計時器與已註冊的等待操作等。
ThreadPool 實例,於首次呼叫 QueueUserWorkItem 或註冊計時器/等待操作時自動創建。若您的場景符合以下任一條件,建議改用手動創建的 Thread 實例:
以下範例展示如何利用 ThreadPool.QueueUserWorkItem 併發計算多組費波那契數,並透過 ManualResetEvent 等待所有任務完成。
using System;
using System.Threading;
public class Fibonacci
{
private readonly int _n;
private readonly ManualResetEvent _doneEvent;
private int _fibOfN;
public Fibonacci(int n, ManualResetEvent doneEvent)
{
_n = n;
_doneEvent = doneEvent;
}
// 供線程池呼叫的包裝方法
public void ThreadPoolCallback(Object threadContext)
{
int threadIndex = (int)threadContext;
Console.WriteLine($"執行緒 {threadIndex} 開始計算...");
_fibOfN = Calculate(_n);
Console.WriteLine($"執行緒 {threadIndex} 計算完成。");
_doneEvent.Set(); // 通知主線程任務結束
}
// 遞迴計算第 N 項費波那契數
public int Calculate(int n)
{
if (n <= 1) return n;
return Calculate(n - 1) + Calculate(n - 2);
}
public int N => _n;
public int FibOfN => _fibOfN;
}
public class ThreadPoolExample
{
public static void Main()
{
const int TaskCount = 10;
ManualResetEvent[] doneEvents = new ManualResetEvent[TaskCount];
Fibonacci[] fibArray = new Fibonacci[TaskCount];
Random r = new Random();
Console.WriteLine($"啟動 {TaskCount} 個計算任務...");
for (int i = 0; i < TaskCount; i++)
{
doneEvents[i] = new ManualResetEvent(false);
// 隨機產生 20~39 之間的 N 值
Fibonacci f = new Fibonacci(r.Next(20, 40), doneEvents[i]);
fibArray[i] = f;
// 將工作項加入線程池,i 作為回調參數傳遞
ThreadPool.QueueUserWorkItem(f.ThreadPoolCallback, i);
}
// 等待所有任務完成
WaitHandle.WaitAll(doneEvents);
Console.WriteLine("所有計算任務完成。");
// 輸出結果
for (int i = 0; i < TaskCount; i++)
{
Fibonacci f = fibArray[i];
Console.WriteLine($"Fibonacci({f.N}) = {f.FibOfN}");
}
}
}
QueueUserWorkItem(WaitCallback, Object):第二個參數會原樣傳遞給回調方法,常用於傳遞任務識別碼或上下文資料。ManualResetEvent:每個任務持有一個事件物件,計算完成時呼叫 Set() 釋放等待中的主線程。WaitHandle.WaitAll:阻塞主線程直到所有事件均被觸發,確保結果完整輸出。線程池是 .NET 並行程式設計的基石,善用它能讓程式碼更簡潔、效能更穩健。掌握其適用場景與限制,並在實際專案中靈活選擇 ThreadPool、Task(TPL)或自訂 Thread,是成為高階 C# 開發者的關鍵技能之一。