在.NET中执行Async/Await的两种错误方法
微信公眾號:架構師高級俱樂部
關注可了解更多的編程,架構知識。問題或建議,請公眾號留言;
如果你覺得此文對你有幫助,歡迎轉發
在.NET中執行異步/等待的兩種錯誤方法
在應用開發中,我們為了提高應用程序的吞吐能力或者異步操作來減少耗時,通常會使用多線程來達到目的,而在C#語言中由于async/await必殺技的存在,大多會使用此來簡化多線程操作,async/await的具體使用方式想必大家已爛熟于心,不再贅述,今天主要談談在我們經常所謂的async/await操作真的是正確的嗎?你又真的用對他了嗎?
情景1:消除異步方法優勢
請看下面代碼示例
以上代碼片段在一個異步方法中,此方法在另一個Task中返回一個Task!(Task.Run) 這是多余的。如果該方法已經返回Task,則我們不應該將其包裝在另一個Task中。因此,在這種情況下,解決方案也非常簡單,取消一切不必要的Task:
ServiceClient client = new ServiceClient(); ServiceRequest request = new ServiceRequest(); request.Id = newId; var responseTask = client.GetServicesAsync(request); ServiceResponse response = await responseTask;情景2:濫用Task.Run()
在工作中大多使用Task.Run的代碼給人的印象是異步有助于提高性能。這是正確的,但僅是非常片面的。Async/Await的目的是幫助提高吞吐量。改善性能僅僅是副作用。
因此在工作中會發現各種奇奇怪怪的代碼,例如以下為了配合外部異步方法,又由于內部各種原因沒有實現異步方法,不得不用Task.Run來包裹同步方法而達到語法要求。
MyService client = new MyService(); var responseTask = Task.Run(() => client.GetData(request)); var response = await responseTask;換句話說:使用Task.Run()包裝了一個同步調用。
這里的問題是方法client.GetData()本身并不是異步方法,通過將異步包裝器置于同步方法之上,我們正在做一個稱為“async-over-sync異步超同步”的反模式,這在大多數情況下最終不是一個推薦的做法。不是因為它不起作用(而是起作用),而是因為它效率不高。
之所以如此,原因是很長的,而且涉及很多,如果感興趣可在文章末尾找到Stephen作者相關對此問題的詳細解釋地址[1]。總結一下,以上代碼非常糟糕,因為實現異步的好處是通過在線程不執行任何操作(例如,等待服務響應)時“釋放”線程來提高吞吐量。上面的示例確實釋放了一個線程,它也立即消耗了另一個線程來執行任務包裝的代碼,并且該消耗的線程在等待服務響應時被阻塞。因此,我們沒有提高吞吐量,只是將工作從一個線程轉移到了另一個線程。而且在并發下,以上使用方式在工作中也極大的降低了系統性能!
解決方案可以簡化為:不要對同步方法使用異步包裝器!只需同步調用它們即可。在這種情況下,理論上的性能優勢將被潛在的問題所抵消,這些潛在的問題在最壞的情況下可能包括死鎖。
過度使用Task.Run()有很大安全隱患,尤其在你未搞懂你寫了什么的時候,這種影響在復雜業務和超大并發下出問題非常難排查!在發現性能嚴重影響又找不到原因的時候,請排查出所有使用Task.Run的代碼,確定是否是以上兩種情況,解決他們可能就海闊天空了
摘要
在.NET或者.Netcore中使用Async/Await都是一項技巧,必須謹慎使用。在上面的示例中,開發團隊試圖使他們的應用程序性能更好,但最終由于對他們的代碼過度使用Async/Await而使情況變的難以控制。
總之應該記住兩件事:
不要將異步任務包裝在另一個異步包裝器Task.Run中。
不要在同步調用上使用異步包裝器。
有很多方法可以修正使用異步/等待的ASP.NET代碼。
更多請參考:
[1]:https://devblogs.microsoft.com/pfxteam/should-i-expose-asynchronous-wrappers-for-synchronous-methods/(我應該為同步方法加異步包裝器Task.Run嗎)
總結
以上是生活随笔為你收集整理的在.NET中执行Async/Await的两种错误方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 向大厂看齐!为自己的程序增加自动转储的功
- 下一篇: C#如何安全、高效地玩转任何种类的内存之