微軟從 ASP.NET Framework 4.5 開始支援 async / await 語法,提供開發者在撰寫非同步作業時更輕鬆與容易上手,如果對於 async / await 不夠熟悉可以看黑暗大之前的好文 ASP.NET async 基本心法,但有時在一知半解寫出來的代碼有時殺傷力才會是最可怕的,如果沒有正確使用 async / await 會造成 Thread block 的問題發生,因此在撰寫 async / await 時需要更加小心留意,這篇文章就來介紹最近發現的 Ben.BlockingDetector 套件來解決此問題,若有問題或是錯誤的地方歡迎高手大大給予指導或討論。
為了讓大家更容易了解建立一個範例專案,專案範本使用 ASP.NET Core API 專案
開啟 Visual Studio 的 Nuget 並搜尋 BlockingDetector 並下載
或是可以透過 Nuget Package Console 輸入下列指令
Install-Package Ben.BlockingDetector -Version 0.0.3安裝完畢之後到專案檔底下確認是否有安裝成功
<ItemGroup> <PackageReference Include="Ben.BlockingDetector" Version="0.0.3" /> </ItemGroup>
使用
一開始有提到過 async / await 未正確使用會造成 Thread block 情況發生,這裡舉一個最近遇到的 case 來說明,公司同仁連續幾天反應網站緩慢已嚴重影響正常操作,我們使用記憶體分析工具分析 dump 出來的 memory 檔案後,發現報告內容多個 Thread 都在等待的狀態,再繼續查看 Thread 的 Call Stack 發現緩慢的程式進入點皆為 PostJsonAsync,找到執行的代碼中發現是使用 Task.Result 要求同步方式回傳結果
在看到使用 Task.Result 幾位資深的同仁都笑了,紛紛表示都有踩過這雷,詳細造成原因與細節說明可以參考黑暗大的文章 : await與Task.Result/Task.Wait()的Deadlock問題,文中介紹來龍去脈非常清楚這裡就不在另外說明,在非同步中常見造成 block 原因有下列兩種
解決方案有兩種,第一種是加上 configureAwait(false) 指定任務完成之後不再使用/等待原本的 Thread 執行,另一種則是將呼叫方法也改為 async 非同步方式進行,這兩種方法都能有效改善 block 問題。但此作法僅能在嚴重影響到應用程式運行時才能發現,在專案中如果要避免 Task block 情況發生,可以使用今天要介紹的主角 Ben.BlockingDetector 幫我們達到此目的,Ben.BlockingDetector 套件將會於 CLR 檢測 lock,ManualResetEventSlim,Semaphore{Slim},Task.Wait,Task.Result 等情況,並在遇到時紀錄在 Log 中,Log 內容會記錄 "Blocking method has been invoked and blocked, this can lead to threadpool starvation.",方便開發團隊日後進行追蹤的動作,下面將介紹使用方式
加入 Detector
開啟剛剛建立好的範例專案,在 Startup.cs 中 Configure 加入 app.UseBlockingDetection(); 代碼
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseBlockingDetection(); // other }
調整代碼
為了要示範 block 時的 log,我們調整 ValuesController 中 Get 方法加上 Task.Result 代碼,希望模擬阻塞的程式碼
public class ValuesController : ControllerBase { // GET api/values [HttpGet] public async void Get() { var result = await Task.Run(() => BlockingTask()); } private static int BlockingTask() { // Detected blocking return MethodAsync().Result; } private static async Task<int> MethodAsync() { await Task.Delay(1000); return 5; } }接著啟動專案後並觀察輸出的 console 紀錄,可以清出的看到啟動後就有記錄到 block 的情況發生
透過以上的步驟可以發現使用相當的簡單,套件實際背後運作原理相信對 ASP.NET Core 有一定理解的人也可以想像到,是加上 middleware 層針對執行的 thread 進行 monitor 的動作,若有興趣了解細節運作可以查看其 Github source code : 傳送門
感想
從上列描述可以得知使用上相當的簡單,下一步就可以看開發者要如何 "處理" 監測到的潛在風險代碼,或是將此紀錄丟到 ELK 加入 Daskboard 定期進行追蹤,發現的時候再請同仁協助改掉避免 block 情況影響到應用程式的運作,就看各位開發者大大如何處置了,希望這篇文章有幫助到大家&喜歡 :)
參考
Ben.BlockingDetector
0 意見:
張貼留言