[廚餘回收] 在 ASP.NET Core 中誤用 async void 竟引發了 502(Bad Gateway)

這個是我最近處理的一個問題,使用者回報網站某個功能壞了,而且發現最近時不時網站會顯示 502(Bad Gateway)的畫面,雖然多重新整理幾次它就好了,但是這並不正常,我隨即捲起袖子開始一系列的追查動作,誓言一定要將兇手緝捕歸案。

檢查 GCP 及 Cloudflare 的服務狀態

系統的環境是這樣的,ASP.NET Core 應用程式部署在 CentOS 7 上,服務前面還擋著 Nginx 反向代理,更前面還有 GCP 的 Load Balancing 及 Cloudflare,所以第一步我先到 GCP 跟 Cloudflare 的後台確認服務是否正常?都確認正常後才進行下一步。

查看 Nginx 的錯誤日誌

下一步我撈出 Nginx 的 Error Log,從 Error Log 中我看到滿滿都是下面這一串訊息:

...no live upstreams while connecting to upstream, ...

這句話翻譯過來就是「上游的應用程式掛了」,而當 Nginx 判斷是上游的應用程式掛了的時候,回應給客戶端的就是大大的「502 Bad Gateway」,那問題來了,為什麼 ASP.NET Core 應用程式會掛掉?當下我去查看服務都是正常的,所以我們得再找多一點線索來追查問題。

async void

根據使用者提供的另一個線索「網站的某個功能壞了」,我們繼續往下追查,從程式碼當中我看到了一個近期新加的方法,它使用了 async void,沒錯,它使用了 async void,而且很不幸地它會發生 Exception,更慘的是這個 Exception 沒有被處理。

對 C# 非同步程式設計有了解的朋友,看到這邊應該大致上可以知道是發什麼問題了,async void 是建議應該避免使用的宣告方式,其中一個原因就是當 async void 方法發生 Exception 時無法從呼叫端捕獲,即使加了 try...catch... 也沒用,async void 方法就有點像是我們自己起了另一個 Thread 去執行程式一樣,執行的過程中如果發生 Exception 沒有去處理,Exception 就會一路被往上拋,最終在 AppDomain 層級被捕獲,然後我們的應用程式就掛了。

由於在 ASP.NET Core 應用程式的服務檔設定當中,Restart 我將它設定為 always,RestartSec 是 10,所以應用程式在掛掉之後 10 秒就被重新啟動,這也就說明了為什麼使用者看到 502 Bad Gateway 之後,重新整理個幾次就恢復正常了。

解決問題

知道問題原因要解決它就不難了,我們只要將 async void 改成 async Task,基本上問題就解決了,這個就是 Fire-and-Forgot 的呼叫方式,當我們的方法無論是執行成功還是失敗,都不需要知道的時候,就可以這樣做。

那麼,如果我們需要攔截 Exception 的時候,則要加上 await 關鍵字。

非同步程式設計會為我們帶來一定程度上的效能優勢,拜程式語言及編譯器的進步,我們現在可以很輕鬆地實現非同步程式設計,但是這背後所要遵循的原則、使用上的限制、帶來的副作用,我們都要清楚了解,以上,最近處理的一個小問題分享給大家,希望對大家有一點幫助。

相關資源

C# 指南
ASP.NET 教學
ASP.NET MVC 指引
Azure SQL Database 教學
SQL Server 教學
Xamarin.Forms 教學