CodeGym /課程 /C# SELF /執行緒優先順序與執行緒類型

執行緒優先順序與執行緒類型

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

1. 介紹

假設執行緒就像排隊買冰淇淋的人。有些人安靜地排隊,有些人會大聲喊("我快要趕不上火車了,讓我先!"),店員好像能聽到每個人,但有時會決定先服務某些人——例如抱著哭嬰的媽媽。執行緒的優先順序大概就是這種情況。

在 C#(更精確地說,在 .NET)中,每個執行緒都有一個優先順序——告訴作業系統這個執行緒相對於其他執行緒有多「重要」。這不是對 OS 的硬性命令(沒人保證最高優先的執行緒永遠會得到所有資源),但通常高優先順序的執行緒會獲得較多的 CPU 時間。

這在實務上在哪裡有用?

  • 在使用者介面:例如畫面更新不應該被低優先的耗時計算卡住。
  • 在遊戲裡:渲染幀比背景地圖生成更重要。
  • 在需要即時反應的任務:控制硬體、訊號處理等等。

2. 執行緒優先順序:運作方式

在 C# 中操作執行緒優先順序很簡單——Thread 物件有個屬性 Priority。它的值來自列舉 ThreadPriority

描述
Lowest
最低的優先順序
BelowNormal
低於一般
Normal
一般(預設)
AboveNormal
高於一般
Highest
最高的優先順序

程式範例:

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Thread lowPriorityThread = new Thread(PrintLowPriority);
        Thread highPriorityThread = new Thread(PrintHighPriority);

        lowPriorityThread.Priority = ThreadPriority.Lowest;
        highPriorityThread.Priority = ThreadPriority.Highest;

        lowPriorityThread.Start();
        highPriorityThread.Start();
    }

    static void PrintLowPriority()
    {
        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine("低優先順序: " + i);
            Thread.Sleep(10); // 加點延遲,好看出差別
        }
    }

    static void PrintHighPriority()
    {
        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine("高優先順序: " + i);
            Thread.Sleep(10);
        }
    }
}

注意

在實務上,特別是在現代多核系統與受管理的 .NET 環境裡,優先順序只是給作業系統的建議。OS(與 .NET)會嘗試分配更多時間給高優先順序的執行緒,但沒有硬性保證。例如,如果一個執行緒把 CPU 全耗完且設為 Highest,使用者介面可能會卡住,其他執行緒也會受影響。

3. 更改執行緒優先順序

執行緒與優先順序

graph LR
    A[執行緒 1 - Lowest] -->|較少的 CPU 時間| OS(作業系統)
    B[執行緒 2 - Normal] --> OS
    C[執行緒 3 - Highest] -->|較多的 CPU 時間| OS
    OS --> CPU(處理器)

為什麼要改優先順序?

看起來好像可以把所有東西都設成 Highest,但這是壞主意!想像如果商店裡所有顧客同時大喊 "先服務我!"

只有在有正當理由時才改變優先順序:

  • 寫日誌的背景執行緒——可以設成 BelowNormalLowest
  • 處理使用者輸入的執行緒——設成 AboveNormal
  • CPU 密集但不那麼即時的任務——低優先順序。

小技巧:如果應用程式變慢,檢查是否有「囂張」的高優先執行緒在搶資源,影響其他工作。

4. .NET 裡的執行緒類別(類型)

在 C# 中通常區分兩種執行緒:foregroundbackground。有趣的是,"background" 並不是字面上一直在「背景工作」的意思。接下來解釋。

Foreground 執行緒(主要執行緒)

  • 預設情況下你建立的執行緒都是 foreground。
  • 只要程序中還有至少一個 foreground 執行緒活著,應用程式就不會結束,即使其他都已經「死掉」。
  • 例子:程式的主執行緒(Main),以及透過 new Thread() 建立而且沒改動的執行緒。

Background 執行緒(次要執行緒)

  • 如果所有的 foreground 執行緒都結束,只剩下 background 執行緒,程序會直接結束,這些 background 執行緒會被無預警中止。
  • 用於次要任務:寫日誌、背景發送統計等。
  • 要把執行緒設為 background:把屬性 IsBackground 設為 true
Thread t = new Thread(SomeMethod);
t.IsBackground = true;
t.Start();

差異示範

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Thread backgroundThread = new Thread(() =>
        {
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("Background thread 工作中... " + i);
                Thread.Sleep(500);
            }
            Console.WriteLine("Background 執行緒 已完成。");
        });

        backgroundThread.IsBackground = true; // 把執行緒設為 background!

        backgroundThread.Start();

        Console.WriteLine("Main 將在 1 秒後結束工作。");
        Thread.Sleep(1000); // 等一秒
        Console.WriteLine("Main 已結束。Background 執行緒會怎麼樣?");
    }
}

你會看到什麼?
Main 可能會先結束,而 background 執行緒會在中途被強制停止。這對那些不應阻止應用關閉的任務很方便。

5. 執行緒的種類

ThreadPool(執行緒池)

  • ThreadPool 是個管理執行緒池的機制,避免建立太多執行緒。
  • 用在任務多且每個任務很短的場景:並行處理請求、非同步操作等。
  • 來自執行緒池的執行緒通常是 background——它們不會阻止應用程式結束。

範例:在執行緒池裡執行程式碼

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        ThreadPool.QueueUserWorkItem(DoWorkInThreadPool, "背景任務");
        Console.WriteLine("Main 正在結束工作。");
        Thread.Sleep(500);
    }

    static void DoWorkInThreadPool(object? state)
    {
        Console.WriteLine("來自執行緒池的執行緒: " + state);
        Thread.Sleep(1000); // 嘗試睡眠
        Console.WriteLine("來自執行緒池的執行緒 已完成!");
    }
}

要注意的:
如果 Main 比執行緒池的工作先結束,執行緒池的執行緒可能會被中斷。如果需要保證完成,請使用 foreground 執行緒或明確等待。

多執行緒的演進:Task、async/await

在現代應用中很少手動建立 Thread。通常會使用 Task 和非同步方法。重點是:

  • 來自執行緒池的執行緒與 Task 通常運行在 background 執行緒上。
  • 對於大多數情況不需要改變優先順序——遵循常識與最佳實務即可。

6. 有用的細節

執行緒的屬性與行為

屬性 Foreground Thread Background Thread ThreadPool Thread
IsBackground
false 預設 true true
應用程式結束 只要還有一個 foreground 執行緒活著,應用程式不會結束 如果所有 foreground 執行緒結束,應用程式會結束 應用程式會在 Main 結束後結束
控制 完全控制 完全控制 沒有直接控制
適用場景 長期、重要的任務(例如資料庫伺服器) 非關鍵任務、背景操作、日誌記錄 短小任務、Task、非同步操作
優先順序 可以設定 可以設定 不建議更改

不那麼明顯的小技巧與備註

  • 只能對普通的執行緒(Thread)改變優先順序,不能對執行緒池裡的任務(Task, ThreadPool)改變。
  • 執行緒池裡的執行緒通常具有 Normal 優先順序(不可更改)。
  • Taskasync/await 是更現代的做法,優先順序與背景執行的細節通常被「包在後面」,不需直接管理。

7. 關於優先順序與執行緒類型的常見錯誤

錯誤 #1:濫用優先順序。
把所有執行緒都設定為 Highest 或全部改為 Lowest 而沒有合理原因,通常沒好處。這會破壞任務執行的平衡,降低應用的回應能力。

錯誤 #2:忽略 background 執行緒會被非預期終止。
如果重要工作(例如儲存資料)在 background 執行緒上執行而你沒有等待它完成,可能會導致資料遺失。background 執行緒在程序結束時會被強制中止。

錯誤 #3:對執行緒優先順序抱有過高期望。
執行緒的 Priority 只是給作業系統的建議,不保證該執行緒會先被執行。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION