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

C# 多執行緒自動管理:ThreadPool 執行緒池完整教學與範例

     編輯:關於C#

前言

在高併發、高吞吐量的應用場景中(如 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:

完整範例:佇列 10 項工作、統計各執行緒負載、等待全部完成

此範例改寫自 MSDN 經典範例,展示:

  1. 建立 ManualResetEvent 等待所有工作完成
  2. 佇列 10 個工作項,每項攜帶 SomeState (含 Cookie ID)
  3. 工作項 Beta:記錄執行緒 HashCode、模擬工作 (Sleep 2秒)、Interlocked.Increment 計數
  4. 最後一項完成時 eventX.Set() 通知主執行緒
  5. 主執行緒列印各執行緒處理項目數 (負載分布)
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 比較

方法作用範例
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)無鎖資料結構基石
特性ThreadThreadPool (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 執行緒數** 是高效能、穩定應用的關鍵。

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