Thread.Join() 讓呼叫執行緒阻塞,直到目標執行緒終止。本文透過三層巢狀執行緒範例解析執行順序,並補充超時、死鎖風險與現代替代方案。
static void Main()
{
Thread t1 = new Thread(F1);
t1.Start();
Console.WriteLine("Main: 1");
t1.Join(); // 阻塞 Main 直到 t1 結束
Console.WriteLine("Main: 2");
}
static void F1()
{
Thread t2 = new Thread(F2);
t2.Start();
t2.Join(); // 阻塞 F1 直到 t2 結束
Console.WriteLine("F1: 1");
}
static void F2()
{
Console.WriteLine("F2: 1");
}
Main: 1 ← Main 印完立即 Join t1
F2: 1 ← t2 執行並結束
F1: 1 ← t1 的 Join 解除,繼續印 F1
Main: 2 ← Main 的 Join 解除,繼續印 Main
Join() 不消耗 CPU:呼叫執行緒進入等待狀態 (WaitSleepJoin)。t1.Join(),Main 不等待 t1,輸出變為 Main:1 → Main:2 → F2:1 → F1:1。
// 等待最多 2 秒
bool finished = t1.Join(TimeSpan.FromSeconds(2));
if (!finished)
Console.WriteLine("執行緒未在 2 秒內結束,決定是否 Abort 或繼續等待");
Invoke/BeginInvoke 回 UI 執行緒,會死鎖。解決:背景執行緒勿同步回 UI,或用 Task + await。| 場景 | Thread.Join | Task/await |
|---|---|---|
| 非同步等待 | 阻塞執行緒 | 釋放執行緒 (await) |
| 例外傳播 | 需手動捕獲 | 自動包裝在 Task 中 |
| 取消支援 | 無 | CancellationToken 原生 |
| 組合多任務 | 困難 | Task.WhenAll/WhenAny |
| 適用對象 | Thread 物件 | Task/Task<T> |
// 現代寫法
var task = Task.Run(() => { /* 耗時工作 */ });
await task; // 非阻塞等待
// 或
task.Wait(); // 同步阻塞 (類似 Join)
Task/await,避免手動管理 Thread。Thread 時,永遠加超時:Join(TimeSpan)。lock) 時呼叫 Join。Task.Run + await,勿用 Thread.Join 卡住 UI。Thread.Join() 是執行緒同步的基礎工具,理解其阻塞語意與巢狀行為,是多執行緒除錯的必備功力。