Asynchronous Programming - 第十章:未完成的回聲
在每一個任務被派遣出去的夜裡,總有一些聲音,無法即刻歸來。它們在平行的執行緒裡交錯穿行,有的抵達了終點,返回一聲輕快的完成;有的在等待中腐蝕,成為無聲的阻塞;有的半途拋下誓言,消失在取消的荒野裡。你以為程式碼寫好了,流向已然明朗,卻不知每個 Task 都像流浪的信件,封存著可能,也埋藏著未竟

TPL,不只是一套平行的工具,它是一場關於 等待與命運 的實驗,每一個 WaitAll、WhenAny、Result,都是開啟分支與結局的咒語。當結果未歸,回聲仍在。在執行緒的深處,有些任務,終將完成;有些回聲,永遠未完
先用最簡單的 Task.Run 建立一個任務,看看它跟一般程式碼的執行順序有什麼不一樣。
1 |
|
執行結果
1 | Main Thread!!!!! |
為什麼只印出 Main Thread!!!!!?
這是因為 Task 預設是 非同步(Asynchronous) 執行的,你叫他去做事(開背景工作)後,主程式不會停下來等他做完。主程式執行緒繼續往下跑,直接把 Main Thread!!!!! 印出來。而背景執行的 Task 需要一點時間(Thread.Sleep(1000)),結果來不及印,主程式就跑完關閉了
Task.Run 的本質是「把工作丟給別的執行緒去做,自己不等結果就繼續往下跑」,所以如果主程式先結束,背景任務根本來不及完成
- Main() 呼叫 SimpleTask(),目前還在「主執行緒(Main Thread)」上跑
- Task.Run(…) 被呼叫,CLR 把 lambda 內容丟到 Thread Pool,這一步「只是在排程任務」,不是在執行內容
- Task.Run 立刻回傳,主執行緒完全沒有等,Task 還在背景準備或剛開始跑
- 主執行緒立刻執行並輸出 Console.WriteLine(“Main Thread!!!!!”)
- SimpleTask() 結束 → Main() 結束,Console 應用程式直接關閉
- 背景 Task 還在 Sleep,就被強制終止

一個方法是 Main 多加一個 Console.Read() 或 Console.ReadLine(),讓主執行緒在結束前卡住,等你看完輸出
1 |
|
✅ 執行結果
1 | Main Thread!!!!! |
因為多了 Console.Read(),程式不會馬上結束,所以等到背景任務跑完後,也會看到 Try Task!!!!!!!!。

現在我們試試看同樣的概念,開兩個 Task,然後確保兩個都做完後再繼續執行主流程
1 |
|
看到上一個例子,我們知道 Task 開始執行我們就不會停在那邊等他完成,會繼續跑主流程,因此這邊我們用 WaitAll 阻塞,預期上,直到 task1、task2 完成前,Main Thread 不會冒出來
✅ 執行結果
1 | Task 1 完成溜 |
當你同時啟動多個 Task 時,主執行緒一樣不會等它們。Task.WaitAll 是一個「同步阻塞(Blocking)」的方法,會卡在那裡,直到你列出的所有 Task 都執行完才往下跑

1 |
|
✅ 執行結果
1 | Done!! |
taskResult 是一個 Task 執行狀況,它裡面包了一個「還沒做好的結果」。當你呼叫 .Result 時,程式會 阻塞(Blocking),如果結果還沒算好,就會在那裡等。等到結果算好了,就把值回傳給你

Task.WhenAny怎麼做多任務競賽(誰先完成)CancellationToken怎麼拋出取消例外try/catch/finally怎麼正確分辨Completed、Canceled、Faulted三種狀態- 怎麼把同步輸入(
Console.ReadKey)包成Task跟Delay比賽
1️⃣ 先準備好取消功能
1 | var cts = new CancellationTokenSource(); |
2️⃣ 開一個 Task,裡面邊等輸入邊計時
用 Console.ReadKey() 讓使用者按 A/B/C 其中一個按鍵。每秒檢查一次,用 Task.WhenAny 確認使用者輸入 or 時間到哪一個先

3️⃣ 如果輸入了,就依輸入執行不同分支
- A:正常完成,回傳 “OK”
- B:拋例外,模擬異常狀況
- C:呼叫取消,丟出 OperationCanceledException
- 其他:回傳 Unknown
4️⃣ 5 秒都沒輸入就回傳 “No Input!”
這時 Task 也算是正常完成
5️⃣ 主流程 try/catch/finally 分別處理三種可能結果
- OperationCanceledException → Canceled
- 其他 Exception → Faulted
- 正常 → Completed
完整實作
1 |
|

Task 沒有「狀態設定器」,只有「結果推論器」,CLR 只是根據你離開方法的方式,自動幫你分類。只要你看到一個 Task 是 Completed,那代表「裡面的程式碼真的沒有炸掉」。這是 async/await 能被信任的根本原因,Task 的狀態是由「你怎麼結束那段程式碼」決定的!
🔵 1. 什麼都不輸入 → 正常結束
1 | Please Choose a Status : A : Completed, B : Fault : C : Cancel |
Console.ReadKey() 沒有輸入,delayTask 每次都先完成
5 次迴圈後,直接回傳 “No Input!”,代表任務是正常完成的,狀態是 Completed

🟢 2. 5 秒內輸入 A → 正常完成
1 | Please Choose a Status : A : Completed, B : Fault : C : Cancel |
Console.ReadKey() 輸入 A 後,inputTask 先完成。Task 直接走到 return “OK”。狀態是 Completed。
🔴 3. 5 秒內輸入 B → 發生例外
1 | Please Choose a Status : A : Completed, B : Fault : C : Cancel |
B 會執行 throw new ApplicationException。這會讓 Task 變成 Faulted 狀態(裡面有 Exception)。進入 catch (Exception) 區塊,印出錯誤訊息。

🟡 4. 5 秒內輸入 C → 取消
1 | Please Choose a Status : A : Completed, B : Fault : C : Cancel |
輸入 C 時,呼叫 cts.Cancel() 並且 ThrowIfCancellationRequested()。這會拋出 OperationCanceledException。Task 變成 Canceled 狀態。進入 catch (OperationCanceledException)










