1. 程式人生 > >多線程和異步編程示例和實踐-踩過的坑

多線程和異步編程示例和實踐-踩過的坑

round 推送 在線 png 很慢 main.c 服務容器 con slist

上兩篇文章,主要介紹了Thread、ThreadPool和TPL

多線程異步編程示例和實踐-Thread和ThreadPool

多線程異步編程示例和實踐-Task

本文中,分享兩則我們在做多線程和異步編程中實際踩過的坑,實際生產環境遇到的問題,以及解決辦法。

1. HttpClient

業務場景:使用HttpClient實現第三方業務推送,當第三方的Http服務器不通、或者返回很慢時

  • 線程數暴漲
  • Asp.Net\Asp.Net MVC場景下,並發多線程導致的線程阻塞:HttpClient.PostAysnc/GetAsync.Result

第一種線程數暴漲的問題,很容易理解,線程執行的慢,線程創建的速度快,一直在執行,導致線程不斷創建線程、線程數不斷暴漲。

根本原因是HttpClient的超時時間配置的太長,這點存於業務設計的問題,理論上簡單請求3s沒響應就該超時了,復雜長時間請求使用異步任務(我們的消息應用中心)處理。

第二種,Asp.Net或者Asp.Net MVC場景下,使用HttpClient,同時GetAsync.Result,異步變同步,這就會出現請求Hang住,線程死鎖、請求沒響應。

根本原因Http異步請求的上下文的互相等待導致的。詳細分析可以參考下面的link:

http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html

有什麽好的解決方案嗎

  • HttpWebRequest代替HttpClient實現各類Http請求,這個類雖然是.net 2.0下的,但是功能的確很強大,應用一般的Http請求沒啥問題!
  • 使用HttpWebRequest實現同步Get、Post請求,避免在Asp.Net場景下使用HttpClient.PostAsync/GetAsync.Result(異步變同步)帶來的線程死鎖,因為Asp.Net機制下Http異步請求的上下文的互相等待導致的。
  • 合理設置Http請求的超時時間,超時即返回,防止線程數暴漲
  • 對於第三方服務器的通信,建議在內存中維護一個聯通性狀態字典,每隔一段時間更新一次,如果不通,直接就不發起Http請求了,這個策略更像是設計上的優化,但是效果很好!

上兩個示例代碼:

技術分享

如果非用HttpClient不可,也是有招的:

技術分享

很簡單,從WebApi,或者Controller這一層全部異步化。前後全部異步。同時,

設置:ConfigureAwait(false)

2. Winform、Console或者Windows服務下,前臺線程異常導致的進程退出

這個問題是很頭疼的,主要是在提供的服務容器運行的插件代碼中,使用了Thread,默認是前臺線程,未正確處理異常,線程異常後,導致主進程退出!!!

業務場景:Windows 服務版本下的消息應用中心,個推使用New Thread推送消息,線程未處理異常,異常後導致Windows Service異常關閉!

結果很頭大,就是整個消息應用中心進程退出了,所有消息處理業務全部受影響!

根本原因:通過New Thread方式創建的線程,系統默認都是前臺線程(IsBack),前臺線程異常如果未處理,會導致應用程序退出,即使註冊了AppDomain.CurrentDomain.UnhandledException事件處理,應用程序依然會掛掉。

解決方案:在線程內部合理處理異常:記日誌、返回值處理,但是不能throw

補充:這裏引出了一個重要的知識點:前臺線程和後臺線程

兩者的區別就是:

  • 應用程序必須運行完所有的前臺線程才可以退出;
  • 對於後臺線程,應用程序則可以不考慮其是否已經運行完畢而直接退出,所有的後臺線程在應用程序退出時都會自動結束。

那麽,Thread、線程池的線程、Task都各自屬於哪一類線程?

使用Thread建立的線程默認情況下是前臺線程,即線程屬性IsBackground=false.

屬於托管線程池的線程(即其 IsThreadPoolThread 屬性為 true 的線程)是後臺線程

Task都是後臺線程:Task需要在其運行過程中至少有一個前臺線程在跑,否則會直接退出.

周國慶

2017/6/16

多線程和異步編程示例和實踐-踩過的坑