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

C# 多線程程式設計:深入理解 ThreadPool 線程池

     編輯:關於C語言

前言

在探索 C# 多線程程式設計的過程中,線程池是一個核心且不可或缺的概念。許多開發者初次接觸時可能感到困惑,透過查閱 MSDN 文件與社群資源,本文整理出關於 ThreadPool 的核心觀念與實作範例,希望能為後續學習者提供清晰的指引。

線程池的核心概念

ThreadPool 是 .NET Framework 提供的一種機制,讓應用程式能更有效地利用多線程。許多應用程式雖然使用多線程,但這些線程往往花費大量時間處於休眠狀態等待事件,且手動管理線程生命週期極為繁瑣。線程池的出現,正是為了解決這些問題——它提供了一組由系統自動管理的背景線程,讓開發者能專注於業務邏輯而非線程調度。

為什麼要使用線程池?

  • 優化資源利用:系統會根據電腦上所有進程的狀況,智慧調整線程時間片分配,提高整體吞吐量。
  • 降低管理成本:無需手動創建、啟動、停止線程,減少因不當管理導致的資源洩漏或死鎖風險。
  • 適用於短小任務:對於執行時間較短、需要並行處理的任務,ThreadPool.QueueUserWorkItem 是最便捷且高效的選擇。

.NET 內部使用線程池的場景

Framework 內部大量依賴線程池處理:非同步委托調用、System.Net 套接字連接、非同步 I/O 完成通知、計時器與已註冊的等待操作等。

線程池的生命週期與限制

  • 每個進程僅擁有一個 ThreadPool 實例,於首次呼叫 QueueUserWorkItem 或註冊計時器/等待操作時自動創建。
  • 線程池中的線程均為背景線程,屬於多線程單元(MTA),不支援單線程單元(STA)需求。
  • 工作項一旦排入佇列,無法取消。

何時不應使用線程池?

若您的場景符合以下任一條件,建議改用手動創建的 Thread 實例:

  1. 需要指定線程優先級。
  2. 任務可能長時間運行,阻塞線程池中其他工作項。
  3. 需要將線程置於 STA 單元(如 UI 元件互動)。
  4. 需要線程的永久標識(如依名稱查找、中止、掛起特定線程)。

實戰範例:使用線程池計算費波那契數列

以下範例展示如何利用 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# 開發者的關鍵技能之一。

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