Async

他們曾經相愛,也曾經深信,只要彼此都願意等,愛就會回來。

她把訊息輸入對話框,卻沒有按下傳送,想等他主動說第一句話。她想:「他如果真的在乎,就會找我。」
他打開視窗看了又看,也沒傳訊息,心想:「她如果還有感覺,就會先聯絡我。」

他們每天打開彼此的聊天室又關上,不是沒有思念,而是不願先伸出手。怕輸、怕低頭、怕被拒絕。
於是他們靜靜地等,在各自的世界,畫了一個誰也跨不過的等待邊界。

這不是不愛,是兩個 .Result() 卡在彼此門前的靈魂,沒有人願意給對方 await 的空間。

🎵 問題緣起:為什麼 .Result 有時會讓整個程式卡住?

async deadlock

await 與 Task.Result/Task.Wait () 的 Deadlock 問題

在 ASP.NET 舊版框架(非 Core)中,如果你在同步方法中呼叫非同步方法,像這樣:

1
var result = GetDataAsync().Result;

你可能會遇到一個非常棘手的問題:死鎖(Deadlock)。

🎵 死鎖是怎麼發生的?

ASP.NET Framework 中的流程:

  • 主執行緒呼叫 .Result,進入「同步等待」狀態,卡住不動。
  • GetDataAsync() 裡面用 await,遇到像 await Task.Delay(…) 時,會中斷流程、把控制權讓出。
  • 等待完成後,預設會「切回原本的執行緒」來繼續執行。

問題來了:原本的執行緒(主執行緒)正在 .Result 那裡等你。但同時,你要回去的執行緒被你自己卡住了 → 互相等待 → ❌ 死鎖。這一切的核心關鍵是:SynchronizationContext。它會記住「原本在哪條執行緒開始」,然後強迫 await 結束後一定要回來。

🎵 ASP.NET Core:不再死鎖

ASP.NET Core 拿掉了 SynchronizationContext,變成這樣:

“任務完成後,誰有空就接,不一定要回原來的執行緒。”

這意味著,await 後續不需要再等主執行緒空出來,所以就算主執行緒 .Result 卡在那,也不會阻止 await 的後續被別的執行緒執行,這樣就不會造成死鎖了。

🎵 那主執行緒還在等嗎?

是的,只要用了 .Result 或 .Wait(),主執行緒就還在等待任務完成。只是這次,它不再是唯一能「收尾」的人。

🧠 換句話說:主執行緒會等結果,但不是非得由它自己完成任務。

在 .NET Core 中,任務完成後可以由任意 ThreadPool 執行緒跑完 await 後續的邏輯,再把結果交回 .Result。

🎵 那如果我用 await 而不是 .Result 呢?

這是非同步程式的本質優勢,await 不會阻塞執行緒,它會把「後面要做的事」記下來(continuation),等有空的執行緒再來做,當下執行緒(例如主執行緒)就被「釋放」,可以去處理別的事,如此一來

少量執行緒也能處理大量請求 → 非同步的威力!

🎵 結語:有些等待,是互相讓步才能抵達的終點

他們明明都還放不下,卻又都太驕傲。你等我回應,我等你先問。於是我們都站在自己心門的另一端,渴望被理解,卻都不願先理解。非同步死鎖亦然。.Result 執著於原地等待,拒絕放手;await 想回到原點續寫後話,卻被拒於門外。

他們的故事不是結束於「沒有結果」,而是卡在「沒人讓步」。await 的哲學是這樣的:我相信你會完成,我暫時離開,但我有留下繼續的指引。這不是逃避,而是信任。有時我們在程式裡學到的等待之道,也該應用在人生。不是非得誰先低頭,而是先鬆開手,才能讓結果流動。讓愛,也讓錯誤,都有一條回來的路。