CodeGym /課程 /C# SELF /使用類別 Thread 啟動執行緒

使用類別 Thread 啟動執行緒

C# SELF
等級 55, 課堂 1
開放

1. 介紹

如果把一個 process 想像成超級市場,執行緒(thread)就是不同收銀台的收銀員,他們同時服務顧客。所有收銀員都在同一家店裡工作,但每個人並行執行自己的任務,這樣整體工作會更快更有效率。執行緒讓一個 process 裡能同時做多件事,分享共同資源並協調應用程式內部的工作。最重要的優點是:如果 CPU 支援多工,執行緒可以真正在平行上運作。

實務上需要執行緒的情況

為什麼我們要啟動多個執行緒?下面是幾個常見情境:

  • 你在寫一個有 GUI 的應用程式,不希望在長時間操作時介面「當掉」。
  • 你需要同時下載多個檔案。
  • 在遊戲中,敵人應該獨立思考,不受其他邏輯影響。

很驚訝的是,許多程式仍然會因為不當使用執行緒而「當機」。今天我們會學到如何避免這類問題。

類別 Thread:手動多執行緒的基礎

類別 Thread 是 .NET 多執行緒編程中的老古董。雖然有更新穎的工具(Task、async/await)出現,但學習用 Thread 操作仍然有價值,特別是如果你想從零開始理解執行緒的運作。

建立執行緒的流程

  1. 建立一個 Thread 物件,並傳入要在執行緒中執行的方法。
  2. 使用 Start() 啟動執行緒。
  3. (可選)觀察發生了什麼 — 因為一切可能是同時發生的!

2. 使用 Thread 啟動執行緒

我們來實際體驗平行處理的魔力。加一個小類別到範例應用裡,負責數到某個數字並顯示進度。我們會把這個工作放到獨立執行緒去執行。

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Console.WriteLine("主執行緒已啟動!");

        // 建立執行緒物件,指定要執行的方法
        Thread workerThread = new Thread(CountToTen);

        // 啟動新執行緒
        workerThread.Start();

        // 主執行緒也做點事:印出點...
        for (int i = 0; i < 5; i++)
        {
            Console.Write(".");
            Thread.Sleep(500); // 為了好看而暫停
        }

        Console.WriteLine("\n主執行緒結束!");
    }

    static void CountToTen()
    {
        for (int i = 1; i <= 10; i++)
        {
            Console.WriteLine($"[執行緒] 計數: {i}");
            Thread.Sleep(400);
        }
        Console.WriteLine("[執行緒] 完成!");
    }
}

發生了什麼事?

你會在主控台看到點和 "計數: X" 交錯出現。這是多執行緒的第一個跡象!主執行緒印點,而新執行緒計數到 10。他們互不干擾,就像樂團裡的兩位音樂家:一個打鼓,另一個彈鋼琴,各自表現,合起來就是音樂。

3. 如何向執行緒傳遞資料?

有時執行緒不只需要知道要做什麼,還要知道要作用的資料。如果執行緒方法需要參數,該怎麼傳進去?

方式一:使用 lambda(匿名方法)

int bounds = 7;
Thread t = new Thread(() => CountToNumber(bounds));
t.Start();
static void CountToNumber(int n)
{
    for (int i = 1; i <= n; i++)
    {
        Console.WriteLine($"[執行緒] {i} / {n}");
        Thread.Sleep(300);
    }
}

這裡我們把對目標方法的呼叫包在 lambda 裡,以便傳入參數。這種做法很常見,因為 Thread 期望的是無參數的方法(ThreadStart)。

方式二:使用 ParameterizedThreadStart

也可以用專門的 delegate ParameterizedThreadStart,它接受一個型別為 object 的參數。

Thread t = new Thread(CountToNumberObject);
t.Start(12);

static void CountToNumberObject(object? n)
{
    int max = (int)n!;
    for (int i = 1; i <= max; i++)
    {
        Console.WriteLine($"[執行緒] {i} / {max}");
        Thread.Sleep(200);
    }
}

是的,參數型別是 object,因此需要做型別轉換。這不太理想,但能運作。現代 C# 通常偏好 lambda 的方式。

4. 管理執行緒的生命

我們來看看 Thread 提供了哪些好用的東西。

屬性 / 方法 用途
Thread.Start()
啟動執行緒(執行建立時指定的方法)
Thread.Join()
等待執行緒結束(阻塞呼叫執行緒直到另一個執行緒完成)
Thread.IsAlive
檢查執行緒當前是否在運行(true/false)
Thread.Name
給執行緒命名(對除錯有幫助)
Thread.CurrentThread
取得代表當前執行緒的物件
Thread.Sleep(ms)
讓當前執行緒暫停 ms 毫秒

範例:等待執行緒結束

有時候需要讓主執行緒等候輔助執行緒完成工作。

Thread t = new Thread(() =>
{
    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine($"[第二執行緒] {i}");
        Thread.Sleep(300);
    }
});
t.Start();

Console.WriteLine("[主執行緒] 等待第二執行緒結束...");
t.Join(); // 主執行緒在這裡等

Console.WriteLine("[主執行緒] 第二執行緒已結束!");

沒有 Join() 的話,程式可能在執行緒仍在工作時就結束。有了 Join(),主執行緒會耐心等待所有工作完成。

5. 有用的小技巧

給執行緒命名:避免混亂

為了除錯可以給執行緒設名:

Thread t = new Thread(() =>
{
    Console.WriteLine($"這在執行緒執行: {Thread.CurrentThread.Name}");
});
t.Name = "計數器-執行緒";
t.Start();

當執行緒變多,每個做不同工作時,這會很有幫助。

限制與實務上的趨勢

要說明的是:在現代應用中,透過 Thread 手動管理執行緒已不常見。實務上常用更強大、更智慧的工具(Task、async/await),我們之後會詳細學。但理解執行緒基礎很重要,因為它有助於:

  • 理解 C# 和 .NET 的「內幕」。
  • 面試時(有時會要求你解釋 Thread 與 Task 的差別)。
  • 診斷與修復大型遺留專案中的複雜問題。

總結圖:執行緒的生命週期

stateDiagram-v2
    [*] --> New: Thread 已建立
    New --> Running: Start()
    Running --> Stopped: 方法完成
    Stopped --> [*]

現在你已經會自己建立並啟動 C# 的執行緒。你不再只是火車裡的乘客,而是能同時駕駛好幾節車廂的司機!接下來會學到執行緒的生命週期管理、同步以及平行化的新領域。

6. 使用 Thread 時常見錯誤與技巧

錯誤 #1:忘記啟動執行緒。
常見情況是建立了 Thread 物件,卻忘了呼叫 Start()。結果執行緒沒開始跑,問題不容易立刻發現。

錯誤 #2:在沒有同步機制下修改共享資料。
如果多個執行緒同時修改同一個變數卻沒保護,就會出問題!就像兩個收銀員從同一個錢箱拿找零——很快就會亂成一團。

錯誤 #3:使用已過時或不安全的方法。
不要使用 Thread.Suspend()、Thread.Resume() 之類的方法——它們被視為危險且過時。用其他方式管理執行緒生命週期。

錯誤 #4:執行緒內未處理的例外。
如果執行緒內發生例外且未被攔截,該執行緒會終止,而主執行緒可能沒發現!把執行緒的程式碼包在 try-catch 中來捕捉並記錄錯誤。

Thread t = new Thread(() =>
{
    try
    {
        // ... 你的程式碼
    }
    catch (Exception ex)
    {
        // 記錄錯誤
        Console.WriteLine($"[執行緒] 錯誤: {ex.Message}");
    }
});
t.Start();
2
任務
C# SELF, 等級 55, 課堂 1
上鎖
等待執行緒結束 (Join)
等待執行緒結束 (Join)
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION