摘自電子書《.NET 本事-非同步程式設計》的第二章(請參閱電子書以獲得最新內容)....

Scott Hanselman 曾就非同步程式設計的議題訪問了 Damian Edwards 和 Levi Broderick。那次訪談的標題有點聳/動,叫做:「.NET 程式設計師所知道的非同步程式設計觀念都錯了」(Everything .NET Programmers Know About Asynchronous Programming is Wrong)。雖然是 2012 年 7 月的對話,但有些內容仍值得參考(主要是針對 ASP.NET 應用程式),故在此整理幾個重點。
註:以下第 (4) 點並非來自 Hanselman 的訪談內容,只因為同樣涉及非同步程式設計的實務建議,故一併在此討論。

(1) 三種適合非同步處理的場合

依 Damian Edwards 的看法,在 ASP.NET web 應用程式中,只有三種情況才應該使用非同步處理,而且只有第一種情況最常用,另外兩種情況則很少用到。

第一種情況、也是最常見的情況,就是 I/O 處理,包括檔案存取、網路存取、資料庫存取(通常包含檔案和網路存取)等等。比如說,常見的 web service 呼叫就是屬於網路 I/O。

第二種情況是類似儀錶板(dashboard)之類的頁面,它需要一次呈現許多區塊,而各區塊的內容又是透過其他檔案或遠端網路存取的方式取得。針對這種情況,由於一次要在網頁上呈現許多不同來源的資料,與其等到所有區塊的內容都取得之後才顯示,不如以非同步的方式分頭進行,哪個區塊先取到資料,就先顯示那個區塊的內容。

第三種情況則是需要長時間處理的 HTTP 請求。比如說,收到某個 HTTP 請求時,必須啟動一個長時間的工作,並且等待那件工作執行完畢,才將工作的結果傳回用戶端(等待過程中,HTTP 連線仍是持續開著的)。

(2) 執行緒集區耗盡的問題

ASP.NET 是使用 CLR 的執行緒集區裡面的執行緒來處理用戶端發出的 HTTP 請求。如果你的 ASP.NET 應用程式會透過 ThreadPool.QueueUserWorkItem 方法來處理背景工作,就等於是跟 ASP.NET runtime 共用同一個執行緒集區——也就是說,你的應用程式用的執行緒數量越多,ASP.NET 能用來處理用戶端 HTTP 請求的執行續數量就越少。不過,.NET 4.5 已針對此狀況做了改進:CLR 會偵測執行緒集區裡面的執行緒是否不夠用,並且視需要加入新的執行緒(當然,集區大小還是有上限的,不可能無限成長;這點前面已經提過)。

(3) 不要輕易調整預設參數值

早期的 ASP.NET 版本,每個 CPU 核心(core)預設能夠同時處理的最大請求數量是 12。這對許多應用程式來說當然是不夠的,所以當時有些人會想盡辦法調整系統預設組態來提升應用程式的效能。到了 .NET 4.0,這個預設值已經提升到每個 CPU 核心可分配到 5,000 個請求,對於大多數的 ASP.NET 應用程式來說,應該是綽綽有餘,也就不太需要去調整預設組態了——而且別忘了,ASP.NET 前面還有 IIS 這道關卡,同時能夠處理多少 HTTP 請求,並不是完全由 ASP.NET 決定。

(4) 使用專屬執行緒的時機

當你碰到以下幾種特殊場合,才應該考慮使用 new Thread() 這種建立專屬執行緒的方式來處理非同步工作:
  • 欲執行的工作需要花很長的時間才能執行完畢(例如一個小時以上)。碰到這種情況,自行建立專屬的執行緒通常會比使用執行緒集區來得有效率。
  • 你希望某些執行緒擁有特殊優先權。預設情況下,執行緒的優先權是「正常」等級。如果想要讓某執行緒擁有特權,則可以個別建立執行緒並修改其優先權。但一般不建議這麼做就是了。
  • 你希望某些執行緒以前景執行緒的方式運作,以避免工作還沒完成,應用程式就被使用者或其他程序關閉。執行緒集區裡面的執行緒永遠都是背景執行緒,它們有可能還沒完成任務就被 CLR 結束掉。
  • 執行緒開始工作後,你可能需要在某些情況下提前終止執行緒(透過呼叫 Thread 類別的 Abort 方法)。

小結

大致可歸納出這樣的建議:優先考慮 Task-base Asynchronous Pattern(TAP)寫法(Task、async、await),然後第二順位是建立專屬執行緒(new 一個 System.Threading.Thread)抑或使用ThreadPool。至於 APM 和 EAP 等舊式寫法,則儘量少用。

無恥連結:https://leanpub.com/dotnet-async