通過 ASP.NET 非同步編程實現可擴充的應用程式

來源:互聯網
上載者:User

http://msdn.microsoft.com/msdnmag/issues/07/03/WickedCode/Default.aspx?loc=zh#void

您想瞭解秘密嗎?諱莫如深,不可言傳的秘密?一旦揭示,將在 ASP.NET 社區引起巨大的反響,並使 Microsoft 的反對者發出“啊哈!”的驚歎,對嗎?

多數使用 ASP.NET 構建的網站沒有良好的可擴充性。它們受到自我強加的“玻璃天花板”的制約,這種束縛限制了它們每秒可處理的請求的數量。這些網站的擴充性一直良好,直到流量提升到這一無形限制時。然後輸送量開始下降。很快,請求開始失敗,通常返回“伺服器不可用”錯誤。

《MSDN雜誌》曾多次就其根本原因進行過討論。ASP.NET 使用公用語言運行庫 (CLR) 線程池中的線程來處理請求。只要線上程池中存在可用線程,ASP.NET 調度傳入請求就不會有任何麻煩。但是一旦線程池處於飽和狀態(即所有池中的線程忙於處理請求,而沒有可用的線程),則新的請求必須等待線程可用。如果這種僵局變得相當嚴重、隊列到達容量限制,ASP.NET 將束手無策,對於新的請求只能做出“拒絕”響應。

一種解決方案是提高線程池的上限,以建立更多的線程。這是當其客戶報告頻繁遇到“伺服器不可用”錯誤時,開發人員經常採取的方法。另一種經常採用的方法是放棄出現問題的硬體,向 Web 場中添加更多的伺服器。但是,增加線程數或伺服器數並不能從根本上解決這一問題。實際上,它僅僅暫時緩解了存在的設計問題,並非存在於 ASP.NET 中,而是實際網站實現中存在的問題。對於不能擴充的應用程式,實際的問題是線程的缺乏。不能有效使用已存在的線程是問題的所在。

真正可擴充的 ASP.NET 網站充分利用了線程池。這意味著可確保請求處理線程執行代碼,而非等待 I/O 完成。如果由於所有線程都在消耗 CPU 而造成線程池飽和,除了添加伺服器,您幾乎無計可施。

然而,多數 Web 應用程式可以與資料庫、Web 服務或其他外部實體進行通話,並通過強制線程池等待完成資料庫查詢、Web 服務調用和其他 I/O 操作來限制可擴充性。針對資料驅動的網頁的查詢可能要花費千分之幾秒來執行代碼,花幾秒鐘等待資料庫查詢返回。當查詢未完成時,分配給請求的線程無法服務於其他的請求。這就是所謂的玻璃屋頂。如果您要構建具有高度可擴充性的網站,這種情況是您必須避免的。請記住:當涉及輸送量時,除非處理得當,否則 I/O 會成為大問題。

當然,如果 I/O 沒有破壞線程池,則算不上大問題。ASP.NET 支援三種可作為防破壞代理的非同步編程模型。對於社區而言,這些模型大都未知,部分原因在於缺乏相關文檔。瞭解如何以及何時使用這些模型對於構建先進的網站絕對至關重要。

非同步頁面

ASP.NET 支援的這三種非同步編程模型中,首要的、通常也是最有用的是非同步頁面。在這三種模型中,這是唯一針對 ASP.NET 2.0 的。其他支援的模型都是針對版本 1.0 的。

在此,我不再詳細介紹非同步頁面,因為在 2005 年 10 月期的雜誌中,我曾對此進行過討論。(msdn.microsoft.com/msdnmag/issues/05/10/WickedCode)。結論是:如果您有一些頁面要執行相對較長的 I/O 操作,它們就應成為非同步頁面。如果某頁面查詢資料庫,花了 5 秒鐘返回(因為它既返回大量資料,又通過大量載入的串連將目標鎖定到遠端資料庫),線程分配給該請求的 5 秒鐘不可用於其他請求。如果每個請求都照此處理,應用程式將會很快陷入停頓。

圖 1 顯示了非同步頁面是如何解決這一問題的。當請求到達時,由 ASP.NET 為其分配一個線程。該請求開始在該線程中進行處理,當選擇資料庫時,請求將啟動非同步 ADO.NET 查詢,並將線程返回到線程池中。當查詢完成時,ADO.NET 回調到 ASP.NET,ASP.NET 從線程池中調出另一個線程,並恢複處理請求。


圖 1 有效非同步頁面 (單擊該映像獲得較小視圖)
圖 1 有效非同步頁面 (單擊該映像獲得較大視圖)

查詢未完成時,線程池中的任何線程均未使用,以確保所有線程均可用於傳入的請求。非同步處理的請求不能被快速執行。但其他請求可更快地執行,因為它們不必等待線程可用。在進入管道時,請求可引起較少的延遲,整體輸送量會被提升。

圖 2 顯示了根據 SQL Server 資料庫執行資料繫結的非同步頁面的程式碼後置類別。Page_Load 方法調用 AddOnPreRenderCompleteAsync 以註冊開始和結束處理常式。在請求生存期的末期,ASP.NET 調用 Begin 方法,該方法將啟動非同步 ADO.NET 查詢並即刻返回,於是,分配給該請求的線程將返回線程池。當 ADO.NET 表明查詢已經結束時,ASP.NET 將從線程池中檢索線程(不必和以前使用的相同),並調用 End 方法。End 方法獲得查詢結果,請求的其餘部分在執行 End 方法的線程中正常執行。

在圖 2 中未顯示的內容是 ASPX 的 Page 指令中的 Async="true" 屬性。非同步頁面應能夠:提示 ASP.NET 在頁面中實現 IHttpAsyncHandler 介面(稍後將詳細介紹)。同樣未在圖 2 中顯示的是資料庫連接字串,該字串包含自己的 Async="true" 屬性,這樣,ADO.NET 就知道要執行非同步查詢了。

AddOnPreRenderCompleteAsync 是構建非同步頁面的一種方法。另一種方法是調用 RegisterAsyncTask。與 AddOnPreRenderCompleteAsync 方法相比,這種方法具有一些優勢,最重要的是它簡化了在一個請求中執行多個非同步 I/O 操作的任務。有關於此的詳細資料,請參閱 2005 年 10 月期的“超酷代碼”部分。

非同步 HTTP 處理常式

ASP.NET 中的第二個非同步編程模型是非同步 HTTP 處理常式。HTTP 處理常式是一個作為請求終結點的對象。例如,對 ASPX 檔案的請求由針對 ASPX 檔案的 HTTP 處理常式處理。同樣,對 ASMX 檔案的請求由知道如何處理 ASMX 服務的 HTTP 處理常式處理。事實上,ASP.NET 擁有針對多種檔案類型的 HTTP 處理常式。在 web.config 主檔案的 <httpHandlers> 部分(在 ASP.NET 1.x中,其位於 machine.config 中),您可以看到這些檔案類型和相應的 HTTP 處理常式。

通過編寫自訂 HTTP 處理常式,您可以擴充 ASP.NET 以支援其他檔案類型。但是,更有趣的一點是,您可以在 ASHX 檔案中部署 HTTP 處理常式,並將它們用作 HTTP 要求的目標。這是構建動態產生映像或從資料庫中檢索映像的 Web 端點的正確方法。您只需將 <img> 標記(或 Image 控制項)包含在頁面中,並將其指向建立或擷取映像的 ASHX。將目標鎖定到帶有請求的 ASHX 檔案比將目標鎖定到 ASPX 檔案更有效,因為 ASHX 檔案在處理時開銷更少。

根據定義,HTTP 處理常式可實現 IHttpHandler 介面。實現該介面的處理常式不能同步進行處理。圖 3 中的 ASHX 檔案包含一個這類 HTTP 處理常式。在運行時,TerraServiceImageGrabber 在 Microsoft TerraServer Web 服務之外進行多次調用以將城市和州轉換為經度和緯度,檢索衛星映像(如同一塊塊“瓷磚”),然後將映像拼接在一起形成指定位置的複合映像。

結果如圖 4 所示。所顯示的頁麵包含 Image 控制項,其 ImageUrl 屬性將目標鎖定在圖 3 中所顯示的 ASHX 檔案。當使用者選擇了城市和州並單擊按鈕後,HTTP 處理常式則將輸入轉換為衛星映像。

結果令人印象深刻。但這有一個問題。TerraServiceImageGrabber 是如何避免編寫 HTTP 處理常式的完美樣本。想一想。TerraServiceImageGrabber 需要幾秒鐘(至少)完成其所有 Web 服務調用並處理結果。大部分時間僅僅花費在等待 Web 服務調用完成上。對於 ASHX 檔案的重複請求會轉瞬間耗盡 ASP.NET 線程池,阻止應用程式中其他頁面的使用(或者至少使它們排隊等待線程可用)。您不能用這種方法構建可擴充的應用程式,除非您擴充了硬體。但是,當使用正確編寫的軟體通過一台伺服器就能處理負載時,為什麼還要將成千上萬的資金耗費在 Web 場上呢?


圖 4 有效 TerraServiceImageGrabber (單擊該映像獲得較小視圖)
圖 4 有效 TerraServiceImageGrabber (單擊該映像獲得較大視圖)

HTTP 處理常式不必是同步的。通過實現 IHttpAsyncHandler 介面,該介面本身可從 IHttpHandler 派生出來,HTTP 處理常式可以是非同步。如果正確使用,非同步處理常式可更有效地利用 ASP.NET 線程。這可採用與非同步頁面相同的方式來完成。事實上,非同步頁面可利用在 ASP.NET 中將非同步頁面日期提前的非同步處理常式支援。

圖 5 包含圖 3 所示的處理常式的非同步版本。Async-TerraServiceImageGrabber 稍微有點複雜,但具有更高的可擴充性。

當 ASP.NET 調用處理常式的 BeginProcessRequest 方法時,開始非同步處理。通過 TerraService 代理的 BeginConvertPlaceToLonLatPt 方法,BeginProcessRequest 可對 TerraService 進行非同步呼叫。然後,分配給該請求的線程返回線程池中。非同步呼叫完成時,另一個線程被從線程池中調出以執行 ConvertPlaceToLonLatCompleted 方法。該線程會檢索上次調用的結果,進行自己的非同步呼叫,然後返回線程池。這種模式不斷重複直至所有非同步呼叫完成,此時,調用處理常式的 EndProcessRequest 方法,產生的位元影像被返回給要求者。

要阻止 EndProcessRequest 直至最後的 Web 服務調用完成,AsyncTerraServiceImageGrabber 返回來自 BeginProcessRequest 的 IAsyncResult 的自我實現。如果它要返回由 BeginConvertPlaceToLonLatPt 返回的 IAsyncResult,則在第一個 Web 服務調用完成時,需調用 EndProcessRequest(並終止請求)。

實現 IAsyncResult 和 TerraServiceAsyncResult 的類具有可隨時調用以完成請求的公用 CompleteCall 方法。通常,只有在最後的 Web 服務調用完成後,AsyncTerraServiceImageGrabber 才調用 CompleteCall。不過,如果在 BeginProcessRequest 和 EndProcessRequest 之間執行的某一方法拋出異常,處理常式將異常緩衝在私人欄位 (_ex) 中,調用 CompleteCall 以終止請求,然後從 EndProcessReques 中重新拋出異常。否則,異常將丟失,請求將無法完成。

由於 AsyncTerraServiceImageGrabber 使用 ASP.NET 線程的時間只是處理請求所需的總時間的一小部分,因此,AsyncTerraServiceImageGrabber 比其同步版的同類方法具有更高的可擴充性。大部分時間裡,它只是等待非同步 Web 服務調用完成。

理論上,AsyncTerraServiceImageGrabber 還勝過 TerraServiceImageGrabber,因為它不是順序地重複調用 TerraService's GetTile 方法,而是並行調用。不過,實際上,每次只有兩個針對給定 IP 位址的出站調用可以被掛起,除非您提高了運行庫的預設 maxconnection 設定:

<system.net><connectionManagement><add address="*" maxconnection="20" /></connectionManagement></system.net>

 

其他配置設定也可影響並發。有關詳細資料,請參考知識庫文章“從 ASP.NET 應用程式進行 Web 服務請求時出現的爭用、效能不佳和死結等問題”(support.microsoft.com/kb/821268)。

即使每次只執行一個調用,但 AsyncTerraServiceImageGrabber 並不比 TerraServiceImageGrabber 差。它的設計非常出色,因為它儘可能有效地使用了 ASP.NET 線程。

非同步 HTTP 模組

您在 ASP.NET 中可能利用的第三個非同步編程模型是非同步 HTTP 模組。HTTP 模組是位於 ASP.NET 管道中的對象,在管道中,它可以查看甚至修改傳入請求和傳出響應。ASP.NET 中的許多主要服務都是以 HTTP 模組的形式實現的,包括身分識別驗證、授權和輸出緩衝。通過編寫自訂 HTTP 模組並將它們插入管道,您可以擴充 ASP.NET。當您這樣做的時候,一定要認真考慮這些 HTTP 模組是否應當是非同步。

圖 6 包括稱為 RequestLogModule 的簡單、同步 HTTP 模組的原始碼,它在名為 RequestLog.txt 的文字檔中記錄了傳入請求。在網站的 App_Data 目錄下建立該檔案,這樣使用者就無法瀏覽它。(要注意 ASP.NET 作為安全性主體的運行(例如,ASPNET 或網路服務)必須寫入對 App_Data 的使用許可權。)該模組實現 IHttpModule 介面,這是 HTTP 模組的唯一要求。載入該模組時,其 Init 方法會為 HttpApplication.PreRequestHandlerExecute 事件註冊一個處理常式,該程式從每個請求的管道中被觸發。事件處理常式開啟 RequestLog.txt(或在該檔案不存在的情況下建立一個),然後將一行包含關於當前請求的有針對性的資訊寫入其中,包括請求到達的時間和日期、要求者的使用者名稱(如果請求是要進行身分識別驗證的,或者如果身分識別驗證關閉,則要包含要求者的 IP 位址),以及請求的 URL。該模組在 web.config 的 <httpModules> 部分進行註冊,以便在每次應用程式啟動時,提示 ASP.NET 載入該檔案。

RequestLogModule 存在兩方面的問題。首先,每次請求時均要執行 I/O 檔案。其次,它使用請求處理線程來執行 I/O,否則,線程可能被用於為其他傳入請求服務。由於簡單,該模組會導致輸送量損失。通過批處理 I/O 檔案操作,您可能會緩解延遲,更好的方法是使模組非同步(或者最好批處理 I/O 檔案並使模組非同步)。

圖 7 顯示了非同步版本的 RequestLogModule。調用 AsyncRequestLogModule 後,它將執行完全相同的工作,並將分配給請求的線程返回線程池,然後寫入檔案。當寫入完成時,從線程池中調出新的線程,用於完成請求。

如何使 AsyncRequestLogModule 非同步?其 Init 方法調用 HttpApplication.AddOnPreRequestHandlerExecuteAsync 以便為 PreRequestHandlerExecute 事件註冊 Begin 和 End 方法。HttpApplication 類包含針對其他 per-request 事件的其他 AddOn 方法。例如,HTTP 模組可以調用 AddOnBeginRequestAsync 以便為 BeginRequest 事件註冊非同步處理常式。AsyncRequestLogModule 的 BeginPreRequestHandlerExecute 方法使用 Framework 的 FileStream.BeginWrite 方法來開始非同步寫入。BeginPreRequestHandlerExecute 返回時,線程返回線程池。

AsyncRequestLogModule 包含一些值得特別一提的線程同步邏輯。運行在多個線程中的多個請求可能要同時寫入記錄檔。為了確保並發寫入不會相互覆蓋,AsyncRequestLogModule 在由所有模組執行個體共用的私人欄位中儲存了下一個寫入在檔案中的位置 (_position)。每次調用 BeginWrite 之前,模組從欄位中讀取該位置並更新欄位以指向要寫入該檔案的內容的第一個位元組。讀取並更新 _position 的邏輯包含在 lock 語句中,這樣每次就有不止一個線程可執行它。這防止了在一個線程有機會更新位置之前,另一個線程讀取該位置。

現在,談談不足之處。對於未使用線程池中另一個線程的 BeginWrite,FileStream 構建函數的 isAsync 參數必須設定為“true”,正如我在樣本中所做的那樣。不過,使用 FileStream.BeginWrite 啟動非同步寫入的一個已知結果是無法保證寫入實際上是非同步,即使您已經請求非同步作業。如果確信同步 I/O 更快,Windows 保留同步執行非同步 I/O 檔案的權利。有關詳細資料,請參閱 support.microsoft.com/kb/156932 上的知識庫文章。好的方面是如 Windows 同步寫入請求日誌,理論上講,寫入可以更快執行,這樣它們對主應用程式可擴充性造成的影響最小。

總結

非同步編程是儘可能高效地使用 ASP.NET 線程池來構建擴充性更強的應用程式的一種很好的方法。以往,我很少看到 ASP.NET 開發人員使用非同步編程模型,部分原因在於他們並不知道存在這些模型。不要讓稀疏文檔成為您的“攔路虎”,從現在起就開始非同步思考,今後您將會構建出更好的應用程式。

請注意,本文提供了 C# 和 Visual Basic 版本的可下載範例程式碼。我常常收到要求提供 Visual Basic 版樣本的電子郵件。這一次,您不必再問了,我已經提供了該版本的樣本!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.