在開發功能完善的網路應用程式時,執行緒幾乎是不可或缺的元素。一個稍具規模的網路程式,內部往往運行著多條執行緒。當執行緒數量超過兩條時,我們便稱之為多執行緒應用程式。網路程式之所以與執行緒緊密纏繞,根本原因在於網路通訊充滿不確定性——最常見的便是網路阻塞與等待。若在主執行緒中同步處理這些耗時操作,程式會出現回應遲鈍、運行緩慢、甚至假死等問題。將這些耗時任務移至背景執行緒處理,能顯著提升程式的執行效能、回應速度與擴展性。
然而,執行緒是把雙面刃。盲目濫用不僅無法提升效能,反而可能產生大量「程式垃圾」,甚至導致程式結束時因執行緒未正確銷毀而掛起。因此,若你的代碼邏輯已足夠高效,建議優先考慮單執行緒方案。若決定使用多執行緒,請務必先深入理解 Windows 執行緒的運作原理與機制。
在 C# 中,所有執行緒操作均圍繞 System.Threading 命名空間下的 Thread 類別展開。最常用的建構函式語法如下:
public Thread(ThreadStart start);
參數 start 為 ThreadStart 委派,指向執行緒啟動時要執行的方法。Thread 類別還提供豐富的方法與屬性來控制執行緒生命週期:
| 方法 | 說明 |
|---|---|
Abort() | 終止執行緒,會拋出 ThreadAbortException |
Interrupt() | 中斷處於 WaitSleepJoin 狀態的執行緒 |
Join() | 阻塞呼叫執行緒,直到目標執行緒終止 |
Sleep(ms) | 讓當前執行緒暫停指定毫秒數 |
Start() | 將執行緒狀態設為 Running,由作業系統排程執行 |
Suspend()/Resume() | 掛起/恢復執行緒(已過時,不建議使用) |
| 屬性 | 說明 |
|---|---|
IsAlive | 取得執行緒是否仍在執行 |
IsBackground | 設定/取得是否為背景執行緒 |
Priority | 執行緒排程優先權 |
ThreadState | 取得執行緒詳細狀態 |
Name | 執行緒名稱,便於除錯 |
注意: .NET 執行緒執行完畢通常會自動銷毀。若需強制終止,可呼叫 Abort(),但若執行緒持有未釋放的非受控資源,仍可能導致資源洩漏。
程式透過對指定 IP 範圍進行 DNS 反向解析:若能解析出主機名稱,則判定該 IP 在線;若拋出例外,則判定離線。為直觀展示多執行緒優勢,本文先實作單執行緒版本,再改造為多執行緒版本,最後對比效能。
介面包含四個 NumericUpDown 組成 IP 區間、一個 ListBox 顯示結果、一個 ProgressBar 顯示進度、一個 Button 觸發掃描。核心邏輯如下:
private void button1_Click(object sender, EventArgs e)
{
listBox1.Items.Clear();
DateTime startTime = DateTime.Now;
string mask = $"{numericUpDown1.Value}.{numericUpDown2.Value}.{numericUpDown3.Value}.";
int min = (int)numericUpDown4.Value;
int max = (int)numericUpDown5.Value;
if (min > max) { MessageBox.Show("IP 區間不合法"); return; }
progressBar1.Minimum = min;
progressBar1.Maximum = max;
for (int i = min; i <= max; i++)
{
string ip = mask + i;
IPAddress ipAddr = IPAddress.Parse(ip);
try
{
IPHostEntry host = Dns.GetHostByAddress(ipAddr);
listBox1.Items.Add($"{ip} 名稱為:{host.HostName}");
}
catch
{
listBox1.Items.Add($"{ip} 主機沒有響應!");
}
progressBar1.Value = i;
}
TimeSpan ts = DateTime.Now - startTime;
label4.Text = $"{ts.Seconds}秒";
MessageBox.Show("掃描完成!");
progressBar1.Value = min;
}
測試掃描 10.138.198.1~10 共 10 個 IP,耗時 43 秒。程式循序執行,總耗時與 IP 數量成正比。

改造需解決兩大難點:
步驟 1:匯入命名空間
using System.Threading;
步驟 2:定義委派與共用變數
public delegate void UpdateList(string ip, string hostName);
private DateTime startTime;
步驟 3:建立掃描工作類別 Ping
public class Ping
{
public UpdateList Callback;
public string IpAddress;
public string HostName;
public void Scan()
{
IPAddress ip = IPAddress.Parse(IpAddress);
try
{
IPHostEntry host = Dns.GetHostByAddress(ip);
HostName = host.HostName;
}
catch
{
HostName = " 主機沒有響應!";
}
Callback?.Invoke(IpAddress, HostName);
}
}
步驟 4:實作 UI 更新回呼(需執行緒安全)
void UpdateMyList(string ip, string hostName)
{
lock (listBox1)
{
listBox1.Items.Add($"{ip} {hostName}");
if (progressBar1.Value < progressBar1.Maximum) progressBar1.Value++;
if (progressBar1.Value == progressBar1.Maximum)
{
TimeSpan ts = DateTime.Now - startTime;
label4.Text = $"{ts.Seconds}秒";
MessageBox.Show("掃描完成!");
progressBar1.Value = progressBar1.Minimum;
}
}
}
步驟 5:重寫按鈕點擊事件,為每個 IP 建立一條執行緒
private void button1_Click(object sender, EventArgs e)
{
listBox1.Items.Clear();
startTime = DateTime.Now;
string mask = $"{numericUpDown1.Value}.{numericUpDown2.Value}.{numericUpDown3.Value}.";
int min = (int)numericUpDown4.Value;
int max = (int)numericUpDown5.Value;
if (min > max) { MessageBox.Show("IP 區間不合法"); return; }
int threadCount = max - min + 1;
Thread[] threads = new Thread[threadCount];
progressBar1.Minimum = min;
progressBar1.Maximum = max + 1;
progressBar1.Value = min;
for (int i = min; i <= max; i++)
{
int idx = max - i;
var worker = new Ping { IpAddress = mask + i, Callback = new UpdateList(UpdateMyList) };
threads[idx] = new Thread(new ThreadStart(worker.Scan));
threads[idx].Start();
}
}
同樣掃描 10 個 IP,多執行緒版本耗時僅 10 秒。無論掃描多少 IP,總耗時大致維持在單次 DNS 解析的最長等待時間左右,因為所有解析並行進行。這充分證明多執行緒在網路 I/O 密集型任務中的巨大優勢。

DateTime 計算執行耗時的技巧。多執行緒雖強大,但複雜度亦高。若尚感模糊,建議先鞏固作業系統執行緒原理,再回頭消化本文實作細節。後續文章將深入探討執行緒同步、執行緒池、非同步模式等進階主題。