使用 ASP.NET 進行非同步和 CPU 密集型操作
ASP.NET 中 async 受到讚譽的原因之一是遵循 Nodejs 非同步平台,它通過釋放執行緒來處理後續請求等,從而提高了可擴展性。
然而,我已經讀過在Task.Run 中包裝受CPU 限制的程式碼會產生相反的效果,即給伺服器增加更多成本並使用更多執行緒。
顯然,只有真正的非同步操作才會受益,例如發出網路請求或呼叫數據庫。
所以,我的問題如下。關於何時應該非同步操作方法是否有任何明確的指導?
Cleary 先生認為將 CPU 綁定操作封裝在非同步程式碼中是徒勞的。
不完全是,在 ASP.NET 應用程序中包裝 CPU 綁定的非同步程式碼與在 WPF 桌面應用程序中執行此操作之間存在差異。讓我用你的這個陳述來建立我的答案。
您應該將您心中的非同步操作(以最簡單的形式)分類為:
ASP.NET 非同步方法,其中包括:
- CPU 密集型操作,
- 阻塞操作,例如 IO 操作
直接面向使用者的應用程序中的非同步方法,其中包括:
- CPU 密集型操作,
- 和阻塞操作,例如 IO 操作。
我假設通過閱讀 Stephen Cleary 的文章,您已經了解非同步操作的工作方式是,當您執行受 CPU 限制的操作時,該操作將傳遞給執行緒池執行緒,完成後,程序控制返回到它開始的執行緒(除非您進行
.ConfigureAwait(false)呼叫)。當然,這只發生在確實有非同步操作要做的情況下,因為我在這個問題中也想知道自己。另一方面,當涉及到阻塞操作時,情況就有些不同了。在這種情況下,當非同步執行程式碼的執行緒被阻塞時,執行時會注意到它,並“暫停”該執行緒正在完成的工作:保存所有狀態以便稍後可以繼續,然後該執行緒用於執行其他操作。當阻塞操作準備好時——例如,網路呼叫的答案已經到達——然後(我不完全知道它是如何被執行時處理或註意到的,但我試圖為你提供一個高級解釋,所以不是絕對必要的)執行時知道你發起的操作已經準備好繼續,狀態恢復,你的程式碼可以繼續執行。
綜上所述,兩者之間有一個重要區別:
- 在 CPU 密集型的情況下,即使您非同步啟動操作,也有工作要做,您的程式碼不必等待任何事情。
- 但是,在 IO-bound 情況或阻塞情況下,可能有一段時間您的程式碼除了等待之外根本無法做任何事情,因此您可以釋放在該點之前完成處理的執行緒並執行其他操作很有用工作(可能處理另一個請求)同時使用它。
對於直接面向使用者的應用程序,例如 WPF 應用程序,如果您在主執行緒(GUI 執行緒)上執行長時間執行的 CPU 操作,那麼 GUI 執行緒顯然很忙,因此顯得無響應面向使用者,因為通常由 GUI 執行緒處理的任何互動都只會在消息隊列中排隊,並且在 CPU 綁定操作完成之前不會得到處理。
但是,對於 ASP.NET 應用程序,這不是問題,因為應用程序不直接面向使用者,因此他看不到它沒有響應。為什麼通過將工作委派給另一個執行緒沒有任何收穫是因為它仍然會佔用一個執行緒,否則可以做其他工作,因為,好吧,無論需要做什麼都必須完成,它只是不能神奇地完成你。
可以這樣想:你和一個朋友在廚房裡(你和你的朋友是一個執行緒)。你們兩個正在準備點菜。你可以告訴你的朋友切洋蔥,即使你把自己從切洋蔥中解放出來,並且可以處理肉的調味,你的朋友因為切洋蔥而忙碌,所以他不能同時做肉的調味。如果你沒有把切洋蔥的工作委託給他(你已經開始了),而是讓他做調味,同樣的工作也會完成,只是你會節省一點時間,因為你不會需要交換工作環境(本例中的砧板和刀具)。所以簡單地說,你只是通過交換上下文造成了一些成本,而無響應的問題對使用者來說是不可見的。
話雖如此,我在頂部概述的分類可以通過將 ASP.NET 應用程序替換為“任何應用程序對使用者沒有直接可見的界面,因此不會對他們沒有響應”來改進。