中文頁面: http://blog.163.com/chuan_zheng/blog/static/856478720074155351773/
英文原文: http://msdn.microsoft.com/msdnmag/issues/06/07/WebAppFollies/default.aspx
樣本源碼頁:http://msdn.microsoft.com/msdnmag/issues/06/07/WebAppFollies/default.aspx?fig=true#fig4
我看過後印象比較深刻的片斷:
LoadControl 和輸出緩衝
極少有不使用使用者控制項的 ASP.NET 應用程式。在出現主版頁面之前,開發人員使用使用者控制項來提取公用內容,如頁首和頁尾。即使在 ASP.NET 2.0 中,使用者控制項也提供了有效方法來封裝內容和行為以及將頁面分為多個地區,這些地區的緩衝能力可以獨立於作為整體的頁面進行控制(一種稱為段緩衝的特殊輸出緩衝形式)。
使用者控制項可以採用聲明的方式載入,也可以強制載入。強制載入依賴於 Page.LoadControl,它執行個體化使用者控制項並返回控制項引用。如果使用者控制項包含自訂類型的成員(例如,公用屬性),則您可以轉換該引用並從您的代碼訪問自訂成員。圖 1 中的使用者控制項實現名為 BackColor 的屬性。以下代碼載入使用者控制項並向 BackColor 分配一個值:
protected void Page_Load(object sender, EventArgs e)
{
// 載入使用者控制項並將其添加到頁面中
Control control = LoadControl("~/MyUserControl.ascx");
PlaceHolder1.Controls.Add(control);
// 設定其背景色
((MyUserControl)control).BackColor = Color.Yellow;
}
以上代碼實際上很簡單,但卻是一個等待粗心的開發人員掉進去的陷阱。您能找出其中的破綻嗎?
如果您猜到該問題與輸出緩衝有關,那麼您是正確的。正如您所看到的一樣,上述程式碼範例編譯和運行都正常,但是如果嘗試將以下語句(完全合法)添加到 MyUserControl.ascx 中:
<%@ OutputCache Duration="5" VaryByParam="None" %>
則當您下一次運行該頁面時,您將看到 InvalidCastException (oh joy!) 和以下錯誤訊息:
“無法將類型為‘System.Web.UI.PartialCachingControl’的對象轉換為類型‘MyUserControl’。”
因此,此代碼在沒有 OutputCache 指令時運行正常,但如果添加了 OutputCache 指令就會出錯。ASP.NET 不應該以這種方式運行。頁面(和控制項)對於輸出緩衝應該是不可知的。那麼,這代表什麼意思?
問題在於為使用者控制項啟用輸出緩衝時,LoadControl 不再返回對控制項執行個體的引用;相反,它返回對 PartialCachingControl 執行個體的引用,而 PartialCachingControl 可能會也可能不會封裝控制項執行個體,具體取決於控制項的輸出是否被緩衝。因此,如果開發人員調用 LoadControl 以動態載入使用者控制項並且為了訪問控制項特定的方法和屬性而轉換控制項引用,他們必須注意進行該操作的方式,以便不管是否具有 OutputCache 指令,代碼都可以運行。
圖 2 說明動態載入使用者控制項以及轉換返回的控制項引用的正確方法。以下是其工作原理概要:
• |
如果 ASCX 檔案缺少 OutputCache 指令,則 LoadControl 返回一個 MyUserControl 引用。Page_Load 將該引用轉換為 MyUserControl 並設定控制項的 BackColor 屬性。 |
• |
如果 ASCX 檔案包括一個 OutputCache 指令並且控制項的輸出沒有被緩衝,則 LoadControl 返回一個對 PartialCachingControl 的引用,此 PartialCachingControl 的 CachedControl 屬性包含對基礎 MyUserControl 的引用。Page_Load 將 PartialCachingControl.CachedControl 轉換為 MyUserControl 並設定該控制項的 BackColor 屬性。 |
• |
如果 ASCX 檔案包括一個 OutputCache 指令並且控制項的輸出被緩衝,則 LoadControl 返回一個對 PartialCachingControl(其 CachedControl 屬性為空白)的引用。注意,Page_Load 不再繼續執行操作。無法設定控制項的 BackColor 屬性,因為該控制項的輸出來源於輸出緩衝。換句話說,根本沒有要設定屬性的 MyUserControl。 |
不管 .ascx 檔案中是否具有 OutputCache 指令,圖 2中的代碼都將運行。雖然看起來複雜一點,但它會避免煩人的錯誤。簡單並不總是代表易於維護。
檢視狀態:無聲的效能殺手
從某種意義上說,檢視狀態是有史以來最偉大的事情。畢竟,檢視狀態使得頁面和控制項能夠在回傳之間保持狀態。因此,您不必像在傳統的 ASP 中那樣編寫代碼,以防止在單擊按鈕時文字框中的文本消失,或在回傳後重新查詢資料庫和重新綁定 DataGrid。
但是檢視狀態也有缺點:當它增長得過大時,它便成為一個無聲的效能殺手。某些控制項(例如文字框)會根據檢視狀態作出相應判斷。其他控制項(特別是 DataGrid 和 GridView)則根據顯示的資訊量確定檢視狀態。如果 GridView 顯示 200 或 300 行資料,我會望而生畏。即使 ASP.NET 2.0 檢視狀態大致是 ASP.NET 1 x 檢視狀態的一半大小,一個糟糕的 GridView 也可以容易地將瀏覽器和 Web 服務器之間的串連的有效頻寬減少 50% 或更多。
您可以通過將 EnableViewState 設定為 false 來關閉單個控制項的檢視狀態,但某些控制項(特別是 DataGrid)在不能使用檢視狀態時會失去某些功能。控制檢視狀態的更佳解決方案是將其保留在伺服器上。在 ASP.NET 1.x 中,您可以重寫頁面的 LoadPageStateFromPersistenceMedium 和 SavePageStateToPersistenceMedium 方法並按您喜歡的方式處理檢視狀態。圖 4 中的代碼顯示的重寫可防止檢視狀態保留在隱藏欄位中,而將其保留在工作階段狀態中。當與預設工作階段狀態進程模型一起使用時(即,工作階段狀態儲存在記憶體中的 ASP.NET 輔助進程中時),在工作階段狀態中儲存檢視狀態尤其有效。相反,如果工作階段狀態儲存在資料庫中,則只有測試才能顯示在工作階段狀態中保留檢視狀態會提高還是降低效能。
在 ASP.NET 2.0 中使用相同的方法,但是 ASP.NET 2.0 能夠提供更簡單的方法將檢視狀態保留在工作階段狀態中。首先,定義一個自訂頁適配器,其 GetStatePersister 方法返回 .NET Framework SessionPageStatePersister 類的一個執行個體:
public class SessionPageStateAdapter :
System.Web.UI.Adapters.PageAdapter
{
public override PageStatePersister GetStatePersister ()
{
return new SessionPageStatePersister(this.Page);
}
}
然後,通過將 App.browsers 檔案按以下方式放入應用程式的 App_Browsers 檔案夾,將自訂頁適配器註冊為預設頁適配器:
<browsers>
<browser refID="Default">
<controlAdapters>
<adapter controlType="System.Web.UI.Page"
adapterType="SessionPageStateAdapter" />
</controlAdapters>
</browser>
</browsers>
(您可以將檔案命名為您喜歡的任何名稱,只要它的副檔名為 .browsers 即可。)此後,ASP.NET 將載入頁適配器並使用返回的 SessionPageStatePersister 以保留所有頁面狀態,包括檢視狀態。
使用自訂頁適配器的一個缺點是它全域性地作用於應用程式中的每一頁。如果您更願意將其中一些頁面的檢視狀態保留在工作階段狀態中而不保留其他頁面的檢視狀態,請使用圖 4 中顯示的方法。另外,如果使用者在同一會話中建立多個瀏覽器視窗,您使用該方法可能會遇到問題。
未緩衝的角色
以下語句經常出現於 ASP.NET 2.0 應用程式的 web.config 檔案以及介紹 ASP.NET 2.0 角色管理器的樣本中:
<roleManager enabled="true" />
但正如以上所示,該語句確實會對效能產生明顯的負面影響。您知道為什麼嗎?
預設情況下,ASP.NET 2.0 角色管理器不會緩衝角色資料。相反,它會在每次需要確定使用者屬於哪個角色(如果有)時參考角色資料存放區。這意味著一旦使用者經過了身分識別驗證,任何利用角色資料的頁(例如,使用啟用了安全裁減設定的網站圖的頁,以及使用 web.config 中基於角色的 URL 指令進行訪問受到限制的頁)將導致角色管理器查詢角色資料存放區。如果角色儲存在資料庫中,那麼對於每個請求需要訪問多個資料庫的情況,您可以輕鬆地免除訪問多個資料庫。解決方案是配置角色管理器以在 Cookie 中緩衝角色資料:
<roleManager enabled="true" cacheRolesInCookie="true" />
您可以使用其他<roleManager> 屬性控制角色 Cookie 的特徵 — 例如,Cookie 應保持有效期限(以及角色管理器因此返回角色資料庫的頻率)。角色 Cookie 預設情況下是經過簽名和加密的,因此安全風險雖然不為零,但也有所緩解。
返回頁首
線程池飽和
在執行資料庫查詢並等待 15 秒或更長時間來獲得返回的查詢結果時,我經常對看到的實際的 ASP.NET 頁數感到非常驚訝。(我也等待了 15 分鐘才看到查詢結果!)有時,延遲是由於返回的資料量很大而導致的不可避免的無奈結果;而有時,延遲則是由於資料庫的設計不佳導致的。但不管是什麼原因,長時間的資料庫查詢或任何類型的長時間 I/O 操作在 ASP.NET 應用程式中都會導致輸送量的下降。
關於這個問題我以前已經詳細地描述過,所以在此就不再作過多的說明了。我只說一點就夠了,ASP.NET 依賴於有限的線程池處理請求,如果所有線程都被佔用來等待資料庫查詢、Web 服務調用或其他 I/O 操作完成,則在某個操作完成並且釋放出一個線程之前,其他請求都必須排隊等待。當請求排隊時,效能會急劇下降。如果隊列已滿,則 ASP.NET 會使隨後的請求失敗並出現 HTTP 503 錯誤。這種情況不是我們希望在 Web 生產伺服器的生產應用程式上所樂見的。
解決方案非非同步頁面莫屬,這是 ASP.NET 2.0 中最佳卻鮮為人知的功能之一。對非同步頁面的請求從一個線程上開始,但是當它開始一個 I/O 操作時,它將返回該線程以及 ASP.NET 的 IAsyncResult 介面。操作完成後,請求通過 IAsyncResult 通知 ASP.NET,ASP.NET 從池中提取另一個線程並完成對請求的處理。值得注意的是,當 I/O 操作發生時,沒有佔用線程池線程。這樣可以通過阻止其他頁面(不執行較長的 I/O 操作的頁面)的請求在隊列中等待,從而顯著地提高輸送量。
您可以在 MSDNMagazine 的 2005 年 10 月刊中閱讀有關非同步頁面的所有資訊。I/O 綁定而不是電腦綁定且需要很長時間執行的任何頁面很有可能成為非同步頁面。
當我將關於非同步頁面的資訊告知開發人員時,他們經常回答“那真是太棒了,但是我的應用程式中並不需要它們。”對此我回答說:“你們的任何頁面需要查詢資料庫嗎?它們調用 Web 服務嗎?您是否已經檢查 ASP.NET 效能計數器中關於排隊請求和平均等待時間的統計資訊?即使您的應用程式至今運行正常,但是隨著您的客戶規模的增長,應用程式的負載可能會增加。”
實際上,絕大多數實際的 ASP.NET 應用程式都需要非同步頁面。請切記這一點!
返回頁首