Async and Await

實現非同步編程的原因之一經常被誤解為可以改善“性能”。這通常被理解為“它讓我的代碼運行得更快”。我在此告訴你,這個說法完全是錯誤的;非同步編程不會讓你的代碼執行得更快。
非同步編程真正做到的是增加可以同時處理的請求數量,使用同樣的資源。與同步系統相比,同樣數量的執行緒可以在非同步系統中處理更多同步請求。簡而言之,非同步編程改善的不是性能而是吞吐量。
(從更高層面看,非同步代碼的結果看起來像是代碼運行得更快,這導致了前面提到的誤解。但實際上系統只是同時做更多事情,允許更多吞吐量)。
讓我們用一個類比來說明這一點,這絕對是說明一個複雜概念的萬全之策。:)
假設我們有一家餐廳。在這家餐廳裡,下單後,訂單被送到廚房做烹飪。廚房裡有一群廚師,他們的工作是製作每個訂單所需的食物。我們可以使用兩種廚房:同步廚房和非同步廚房。
在同步廚房中,每位廚師在任何給定時間只能接到一個訂單。他們必須完全依靠自己完成該訂單的所有工作。這意味著如果項目需要進烤箱,他們將全神貫注地等在那裡,耐心地等待物品烹飪完成,而不做其他任何事情。因此這間廚房只能同時處理 X 個訂單,X 是廚房中廚師的數量。
在非同步廚房中,廚師不會有閒置時間。他們可能會接到一個訂單並開始製作它,但如果需要等待某件事完成(如在烤箱中烹飪),他們必須去做其他事情。這間廚房可以處理比廚師數量多得多的訂單,因為沒有一個廚師會站著等著發生什麼事。因此,廚房完成的訂單數量(即吞吐量)增加了,儘管每位廚師其實工作速度並沒有比平時快。
說到這,你猜哪種廚房更準確地代表了現實世界…
非同步編程的基礎
當我們記住非同步編程在 ASP.NET 中的目標是提高吞吐量,我們可以開始討論一些在實施它時必須記住的基本事項。以下是需要記住的幾點:
非同步不是多執行緒
記住這一點至關重要,非同步代碼與使用多執行緒的代碼不同。
在多執行緒中,工作是在完全獨立的執行緒上進行的,個別執行緒之間沒有交互。在非同步編程中,我們定義了執行緒可以在未來某個時間點離開並返回的點。
在執行非同步任務的過程中,.NET 會創建一個名為 SynchronizationContext 的東西,它存儲執行緒可在該點重新執行的上下文。注意,執行此操作的執行緒可能與執行該點之前代碼的執行緒相同或不同。
Eric Lippert 在 StackOverflow 上有一篇很棒的解釋文章,如果你想要更詳細地了解非同步與多執行緒的區別。他描述的最重要的一點是:
“執行緒是關於工人;非同步是關於任務。”
非同步作用於 I/O 綁定任務,而不是 CPU 綁定任務
由於非同步編程是關於任務的,我們需要具體說明哪些任務受益於使用它。這些任務被認為是 I/O 綁定的,以區分於 CPU 綁定的任務。
CPU 綁定任務是依賴於機器的計算速度快速執行的任務,比如複雜的數學計算。這些計算會佔用處理器時間,在執行時處理器不需要等待任何其他輸入。這種類型的任務無法從非同步編程中獲益。
I/O 綁定任務則需要來自外部來源的響應。這些外部來源可能包括數據庫、服務、REST API 等。在調用這些來源時,處理器通常需要“等待”它們回應。
在“正常”環境中(即同步環境),執行代碼的執行緒在調用外部來源後會一直等到其回复。而非同步編程允許執行緒在需要等待的點“放置”一個標記,讓執行緒可以稍後在外部來源響應時返回該點(這個標記包括先前提到的 SynchronizationContext)。
非同步編程對 CPU 綁定任務沒有任何好處;因為不涉及“等待”。
非同步像病毒一樣傳播
在.NET代碼庫中實施非同步編程時,會快速地向鄰近代碼傳播。它很像病毒;喜歡感染接觸到的事物。
這意味著在不知道會產生什麼後果的情況下,不要混合使用同步和非同步代碼。實際上,我們將在一次同步應用轉換為非同步應用的示範中(作為單獨文章發佈)看到混合同步和非同步代碼會引發哪些問題。
返回類型
.NET中的非同步代碼僅使用三個返回類型:
– Task:代表正在進行的工作,最終會將控制權返回給調用者。
– Task<T>:代表正在進行的工作,最終會將類型為T的對象返回給調用者。
– void:使方法成為真正的即開即忘方法。
然而,返回 void 的有效用例數量極少。這主要是因為,當返回 void 時,系統無法知道方法何時(甚至是否)完成。此外,當返回 void 時異常處理變得非常麻煩。因此,最好不要從非同步任務中返回 void(儘管有一個值得注意的例外,即事件處理器)。
重構指南
以上所有基本知識適用於從頭開始編寫新的非同步 ASP.NET 應用程序或將同步應用程序重構為非同步應用程序時。但當將同步應用程序重構為非同步應用程序時,還有一條額外的指導原則需要遵守。
自底向上重構以減少依賴項
由於非同步編程像病毒一樣傳播,重構時最好從數據架構的最底層開始,向上進行。
這是我在 CodeMash 演講上的一張幻燈片,我們將用來說明這個指導原則的含義。
在此數據模型中,“根”對象是 User。User 具有 Albums 和 Posts,而 Albums 進一步包含 Photos。
指導方針指出,我們應該從架構中最低的數據級別開始重構,在本例中是 Photo(儘管可以說從 Post 開始)。這是因為 Photo 沒有依賴於其他數據對象的依賴項。由於不存在此類依賴項,當我們重構 Photo 時,尚不必太擔心“病毒”問題。
Leave a Reply