開發高效能 ASP.NET 應用程式

這個主題中的方針會列出您可用來協助將 ASP.NET Web 應用程式之輸送量最大化的技術。這些方針將分別於下列各節中進行討論:

  • 網頁和伺服器控制項處理

  • 狀態管理

  • 資料存取

  • Web 應用程式

  • 編碼實務

網頁和伺服器控制項處理

下列方針將建議有效地使用 ASP.NET Web 網頁和控制項的方法。

  • 避免與伺服器間不必要的往返:有些情況下,不需要使用 ASP.NET 伺服器控制項和執行回傳事件處理。例如,驗證 ASP.NET Web 網頁中使用者輸入的工作,通常可以在資料送至伺服器之前於用戶端上進行。一般而言,如果不需要將要驗證或寫入至資料存放區的資訊轉送至伺服器,則可藉由避免會造成與伺服器間往返的程式碼,來提高網頁的效能和改善使用者感受。您也可以使用用戶端回呼 (Callback) 讀取伺服器中的資料,而不是執行完整的往返作業。如需詳細資訊,請參閱在 ASP.NET 網頁中以程式設計方式實作用戶端回呼但不回傳

    如果您要開發自訂的伺服器控制項,則可考慮讓它們替支援 ECMAScript (JavaScript) 的瀏覽器轉譯用戶端程式碼。透過這樣的方式來使用伺服器控制項,即可大幅降低傳送資訊至 Web 伺服器的次數。如需詳細資訊,請參閱開發自訂的 ASP.NET 伺服器控制項

  • 使用 Page 物件的 IsPostBack 屬性以避免在往返時執行不必要的處理:如果撰寫的程式碼是用來處理伺服器控制項回傳處理,有時您會想要程式碼只在第一次要求網頁時執行,而不是每次回傳時都執行。使用 IsPostBack 屬性,根據該網頁是否是回應伺服器控制項事件而產生的,而有條件地執行程式碼。

  • 只有在需要時才儲存伺服器控制項檢視狀態:自動檢視狀態管理會讓伺服器控制項在往返時重新填入它們的屬性值,而不需要撰寫任何程式碼。然而,這個功能會影響效能,原因在於伺服器控制項的檢視狀態會在隱藏的表單欄位中,來回傳遞給伺服器。了解檢視狀態何時有所助益以及何時會影響網頁效能,是很有幫助的。例如,如果在每次往返時將伺服器控制項繫結至資料,則因為會用資料繫結 (Data Binding) 期間的新值來取代控制項的值,所以儲存的檢視狀態並不怎麼有用。在那種情況下,停用檢視狀態會儲存處理時間,並減少網頁的大小。

    依照預設,檢視狀態將會對所有伺服器控制項啟用。若要停用它,請將控制項的 EnableViewState 屬性設為 false (如下列 DataGrid 伺服器控制項範例中所示):

    <asp:datagrid EnableViewState="false" datasource="..." 
       runat="server"/>
    

    您也可以使用 @ Page 指示詞,針對整個網頁停用檢視狀態。這在您不從網頁回傳至伺服器時很有用處:

    <%@ Page EnableViewState="false" %>
    
    注意事項:

    @ Control 指示詞中也支援 EnableViewState 屬性 (Attribute),以指定是否啟用使用者控制項的檢視狀態。

    若要分析網頁上由伺服器控制項所使用的檢視狀態大小,請在 @ Page 指示詞中併入 trace="true" 屬性,以啟用網頁的追蹤功能。在追蹤輸出中,查看 [控制項階層] 表格的 [Viewstate] 欄位。如需追蹤和如何啟用它的詳細資訊,請參閱 ASP.NET 追蹤概觀

  • 除非有特別的原因需要關閉緩衝功能,否則請開啟它:停用 ASP.NET Web 網頁的緩衝,會造成明顯的效能成本。如需詳細資訊,請參閱 Buffer 屬性。

  • 使用 Transfer 方法 (屬於 Server 物件) 或跨網頁張貼會在相同應用程式的 ASP.NET Web 網頁之間重新導向:如需詳細資訊,請參閱將使用者重新導向至另一網頁

狀態管理

下列方針會建議有效進行狀態管理的方法。

  • 在不使用工作階段狀態時停用它:並非所有應用程式或網頁都需要每位使用者的工作階段狀態。如果不需要工作階段狀態,則應停用它。若要停用網頁的工作階段狀態,請將 @ Page 指示詞中的 EnableSessionState 屬性設為 false (如下列範例所示):

    <%@ Page EnableSessionState="false" %>
    
    注意事項:

    如果網頁需要存取工作階段變數,但不會建立或修改它們,請將 @ Page 指示詞中的 EnableSessionState 屬性設為 ReadOnly。

    您也應停用 XML Web Service 方法的工作階段狀態。如需詳細資訊,請參閱使用 ASP.NET 和 XML Web Service 用戶端建立的 XML Web Service

    若要停用應用程式的工作階段狀態,請在應用程式之 Web.config 檔案的 SessionState 區段中,將 Mode 屬性設為 Off (如下列範例所示):

    <sessionState mode="Off" />
    
  • 選擇符合應用程式需要的適當工作階段狀態提供者:ASP.NET 會提供多種方式來儲存應用程式的工作階段狀態:同處理序 (In-Process) 工作階段狀態、跨處理序 (Out-Of-Process) 工作階段狀態 (如 Windows 服務),以及 SQL Server 資料庫中的跨處理序工作階段狀態 (也可以建立自訂工作階段狀態提供者,將工作階段資料儲存於您所選擇的資料存放區中)。上述每個方式都有其優點,但同處理序工作階段狀態顯然是最快速的方案。如果您只是要在工作階段狀態中存放小量的暫時資料,建議您使用同處理序提供者。如果要將應用程式調整成跨多個處理器或多台電腦,或想要在重新啟動伺服器或處理序時保留工作階段資料,則跨處理序工作階段狀態選項十分有用。如需詳細資訊,請參閱 ASP.NET 工作階段狀態

資料存取

下列方針會建議在應用程式中有效進行資料存取的方法。

  • 使用 SQL Server 和預存程序進行資料存取:在 .NET Framework 所提供的所有資料存取方法中,建議使用 SQL Server 進行資料存取,以建置高效能且可擴充的 Web 應用程式。使用 Managed SQL Server 提供者時,透過使用已編譯的預存程序代替 SQL 命令,可以獲得額外的效能提升。如需使用 SQL Server 預存程序的詳細資訊,請參閱設定參數 (ADO.NET)

  • 使用 SqlDataReader 類別做為快速單向捲動檢視 (Forward-only) 的資料游標SqlDataReader 類別會提供從 SQL Server 資料庫中所擷取的單向捲動檢視資料流。如果您可以在 ASP.NET 應用程式中使用唯讀資料流,SqlDataReader 類別所提供的效能會高於 DataSet 類別。SqlDataReader 類別會使用 SQL Server 的原生 (Native) 網路資料傳輸格式,從資料庫連接中直接讀取資料。例如,繫結至 SqlDataSource 控制項時,您可以將 DataSourceMode 屬性設為 DataReader,即可達到較佳的效能 (使用資料讀取器會遺失一些功能)。再者,SqlDataReader 類別實作了 IEnumerable 介面,它同樣可讓您將資料繫結至伺服器控制項。如需詳細資訊,請參閱 SqlDataReader 類別。如需 ASP.NET 如何存取資料的詳細資訊,請參閱使用 ASP.NET 存取資料

  • 只要可能,即可快取資料和網頁輸出:ASP.NET 提供機制,在不需要動態計算該網頁要求的網頁輸出或資料時,快取這些網頁輸出或資料。此外,設計快取的網頁和資料要求 (特別是在您網站上預期會有繁重流量的區域中) 還可以最佳化那些網頁的效能。適當地使用快取所提高的網站效能會高於使用 .NET Framework 中的任何其他功能。

    使用 ASP.NET 快取時,請注意下列事項。第一,不要快取太多項目。快取每個項目都會有記憶體成本。易於重新計算或鮮少使用的項目不應該快取。第二,不要對快取的項目指定過短的到期時間。快速過期的項目會在快取中造成不必要的重組,而造成額外的清除程式碼和記憶體回收行程工作。使用與 [ASP.NET 應用程式] 效能物件關聯的 [Cache Total Turnover Rate] 效能計數器,即可監視因項目到期所產生的快取重組。高重組率可能代表問題,特別是項目在過期前移除的時候 (這個狀況有時稱為記憶體壓力)。

    如需如何快取網頁輸出和資料要求的詳細資訊,請參閱 ASP.NET 快取概觀

  • 適當地使用 SQL 快取相依性:ASP.NET 會依據您所使用的 SQL Server 版本,支援資料表架構的輪詢和查詢告知。所有 SQL Server 版本都支援資料表架構的輪詢。在資料表架構的輪詢中,如果資料表中有任何項目變更,所有接聽程式 (Listener) 就會失效。這樣可能會導致應用程式中出現不必要的變換。對於具有許多經常變更項目的資料表而言,不建議使用資料表架構的輪詢。例如,不常變更的目錄資料表就會建議使用資料表架構的輪詢。對於會有更多經常更新項目的訂單資料表則不建議使用。SQL Server 2005 支援查詢告知。查詢告知支援特定查詢,則可以減少資料表變更時傳送的告知數目。雖然它比資料表架構的輪詢提供更好的效能,不過它無法縮放為數千筆查詢。

    如需 SQL 快取相依性的詳細資訊,請參閱逐步解說:將 ASP.NET 輸出快取功能與 SQL Server 搭配使用以 SqlCacheDependency 類別在 ASP.NET 中快取

  • 使用資料來源分頁和排序而非 UI (使用者介面) 分頁和排序:資料控制項 (例如 DetailsViewGridView) 的 UI 分頁功能可搭配支援 ICollection 介面的任何資料來源物件使用。對於每個分頁作業而言,資料控制項會查詢整個資料集合的資料來源,並選取要顯示的資料列,而捨棄其餘資料。如果資料來源實作 DataSourceView 而且 CanPage 屬性傳回 true,則資料控制項將會使用資料來源分頁而不是 UI 分頁。在這種情況下,資料控制項會只查詢每個分頁作業所需的資料列。因此,資料來源分頁會比 UI 分頁更有效率。只有 ObjectDataSource 資料來源控制項才支援資料來源分頁。若要在其他資料來源控制項上啟用資料來源分頁,您就必須從資料來源控制項繼承並修改它的行為。

  • 權衡事件驗證的安全性優點與效能成本:衍生自 System.Web.UI.WebControlsSystem.Web.UI.HtmlControls 類別的控制項可以驗證控制項所呈現之使用者介面中產生的事件。這樣可以協助防止控制項回應假冒的事件告知。例如,DetailsView 控制項可以防止處理 Delete 呼叫 (控制項中原本不支援) 並操作成刪除資料。不過,這項驗證會具有某些效能成本。您可以使用 EnableEventValidation 組態項目和 RegisterForEventValidation 方法,控制這項行為。驗證的成本取決於網頁上的控制項數目,而且位於少數百分比範圍中。

    安全性注意事項:

    強烈建議您不要停用事件告知。在停用事件告知以前,您應該確定無法建構任何會對應用程式造成非預期影響的回傳。

  • 除非必要,否則避免使用檢視狀態加密:檢視狀態加密 (Encryption) 可以讓使用者無法讀取隱藏檢視狀態欄位中的值。一般的案例為一種能夠在 DataKeyNames 屬性中帶有識別項欄位的 GridView 控制項。識別項欄位是協調記錄更新所需要的欄位。由於您不想要讓使用者看見識別項,所以可以加密檢視狀態。不過,加密對於初始化具有固定的效能成本,而且具有取決於所加密之檢視狀態大小的額外成本。加密會針對每個頁面載入進行設定,所以每次載入頁面時都會發生相同的效能影響。

  • 使用 SqlDataSource 快取、排序和篩選:如果 SqlDataSource 控制項的 DataSourceMode 屬性設定為 DataSetSqlDataSource 就能夠快取查詢的結果集 (Result Set)。如果以這種方式快取資料,控制項 (以 FilterExpressionSortParameterName 屬性設定) 的篩選和排序作業就會使用快取的資料。在許多情況下,如果您快取整個資料集,並且使用 FilterExpressionSortParameterName 屬性來排序和篩選 (而不是使用 SQL 查詢搭配針對每個選取動作存取資料庫的 "WHERE" 和 "SORT BY" 子句),應用程式執行的速度就會更快。

Web 應用程式

下列方針會建議讓 Web 應用程式整體有效率運作的方法。

  • 考慮使用先行編譯:Web 應用程式會在第一次要求資源 (例如 ASP.NET Web 網頁) 時進行批次編譯。如果沒有編譯應用程式中的網頁,批次編譯 (Compilation) 則會按區塊 (Chunk) 對目錄中的所有網頁進行編譯,進而改善磁碟和記憶體使用量。您可以使用 ASP.NET 編譯工具 (Aspnet_compiler.exe) 對 Web 應用程式進行先行編譯。如果是就地編譯,編譯工具則會呼叫 ASP.NET 執行階段,以使用者要求網站中的網頁時的相同方式來編譯網站。您可以對 Web 應用程式進行先行編譯,使 UI 標記維持不變,或者對網頁進行先行編譯,使原始程式碼不受變更。如需詳細資訊,請參閱 HOW TO:先行編譯 ASP.NET 網站

  • 在 Internet Information Services 5.0 上執行 Web 應用程式跨處理序:根據預設,IIS 5.0 上的 ASP.NET 將會使用跨處理序的背景工作處理序服務要求。這個功能已微調以得到快速輸送量。因為在跨處理序背景工作處理序中執行 ASP.NET 有其功能和益處,所以建議您在實際執行網站上使用這種方法。

  • 定期回收處理序:您應該定期回收處理序,以獲得穩定性和效能。經過一段長時間後,具有記憶體遺漏 (Memory Leak) 和錯誤的資源會影響 Web 伺服器輸送量,而回收處理序會清除這類問題的記憶體。然而,您應該在定期回收的需求與過常回收之間取得平衡,因為停止背景工作處理序、重新載入網頁和重新取得資源和資料的代價,可能會高過回收的益處。

    在使用 IIS 6.0 的 Windows Server 2003 上執行 ASP.NET Web 應用程式,因為 ASP.NET 會使用 IIS 6.0 處理序模型設定,所以不需要調整處理序模型設定。

  • 必要時,針對應用程式調整每個背景工作處理序的執行緒數目:ASP.NET 的要求架構會嘗試在執行要求的執行緒數目與可用資源之間取得平衡。此架構只容許與可用 CPU 能耐相等的並行執行要求數目。這個技術即是 Thread Gating。然而,有些情況下 Thread-Gating 演算法無法正常運作。您可以在Windows 效能監視器中,使用與 [ASP.NET 應用程式] 效能物件關聯的 [Pipeline Instance Count] 效能計數器,監視 Thread Gating。

    ASP.NET Web 網頁呼叫外部資源時 (如執行資料庫存取或 XML Web Service 要求時),該網頁要求一般會停止,直到外部資源回應,以釋出 CPU 處理其他執行緒。如其他要求正在等候處理,且執行緒集區有可用執行緒,則會開始處理等待的要求。結果是 ASP.NET 背景工作處理序或應用程式集區中的並行執行要求和許多等待中執行緒數目過高,妨礙 Web 伺服器的輸送量,進而嚴重影響效能。

    為了緩和這種情形,您可以在 Machine.config 檔案的 processModel 區段中變更 MaxWorkerThreadsMaxIOThreads 屬性,藉以手動設定處理序中執行緒數目的限制。

    注意事項:

    背景工作執行緒適合處理 ASP.NET 要求,而 IO 執行緒則是用於服務檔案、資料庫或 XML Web Service 的資料。

    指定給處理序模型屬性的值是處理序中每個 CPU 之每種執行緒類型的最大數目。對於有兩個處理器的電腦,最大數目是設定值的兩倍。對於有四個處理器的電腦而言,最大值為設定值的四倍。預設值適用於具有一或兩個處理器的電腦,但是對於具有兩個以上處理器的電腦而言,處理序中若有 100 或 200 個執行緒,對效能而言是有害無益的。處理序中有過多的執行緒會因為額外的 Context Switches 而拖慢伺服器的速度,因而造成作業系統將 CPU 循環花費在維護執行緒而不是處理要求上。適當的執行緒數目最好是透過應用程式的效能測試來決定。

  • 若是大量依賴外部資源的應用程式,請考慮在多處理器電腦上啟用 Web Gardening:ASP.NET 處理序模型協助啟用多處理器電腦上的延展性 (Scalability),方法是將工作散發給數個處理序、每個 CPU 都有一個處理序,而每個處理序的處理器相似性是設定給 CPU。這個技術被稱為 Web Gardening。如果您的應用程式使用緩慢的資料庫伺服器,或是呼叫具有外部相依性的 COM 物件 (此處僅指出兩個可能性),則針對應用程式啟用 Web Gardening 將大有助益。然而,在決定針對實際執行網站啟用這個功能之前,應該測試一下應用程式在 Web 處理序區 (Web Garden) 中的執行效果如何。

  • 停用偵錯模式:在部署實際執行應用程式或進行任何效能度量之前,一定要停用偵錯模式。如果啟用偵錯模式,則應用程式的效能可能會減損。如需設定偵錯模式的語法資訊,請參閱編輯 ASP.NET 組態檔

  • 調整 Web 伺服器電腦和特定應用程式的組態檔,以符合您的需求:根據預設,ASP.NET 組態是設定為啟用最多功能的組合,並嘗試容納最常見的案例。部分預設的組態設定可根據所使用的功能加以變更,以提高應用程式的效能。下列清單包含您應考慮的組態設定:

    • 僅針對需要驗證的應用程式啟用驗證:根據預設,ASP.NET 應用程式的驗證模式為 Windows 或整合式 NTLM。在大部分的情況中,最好是停用 Machine.config 檔中的驗證,並在需要驗證之應用程式的 Web.config 檔中啟用驗證。

    • 將應用程式設定為適當的要求和回應編碼設定:ASP.NET 的預設編碼方式是 UTF-8。如果您的應用程式只會使用 ASCII 字元,將應用程式設定為 ASCII,能些微地改善效能。

    • 考慮停用應用程式的 AutoEventWireup:將 Machine.config 檔中的 AutoEventWireup 屬性設為 false,表示不會根據名稱符合將網頁事件繫結至方法 (例如,Page_Load)。如果停用 AutoEventWireup,網頁可能會稍微地提升效能,方法是由您來處理事件關聯 (Wiring),而不是自動執行。

      如果您想要處理網頁事件,請使用下列其中一種策略。第一項策略是覆寫基底類別中的方法。例如,您可以針對頁面載入事件覆寫 Page 物件的 OnLoad 方法,而非使用 Page_Load 方法 (請務必呼叫基底方法來確保所有事件都已引發)。第二項策略是使用 Handles 關鍵字 (在 Visual Basic 中) 或委派連接 (在 C# 中),繫結至事件。

    • 移除要求處理管線中的未使用模組:在伺服器電腦之 Machine.config 檔的 HttpModules 節點中,所有功能預設都會保持使用中狀態。依照應用程式所使用的功能,您也許能夠從要求管線移除不要使用的模組,而稍微地提升效能。檢視每個模組及其功能,並依您的需要加以自訂。例如,如果未在應用程式中使用工作階段狀態和快取輸出,則可從 HttpModules 清單中移除每個項目,因此在未執行任何其他有意義處理的情況下,要求不需要叫用 (Invoke) 這些模組。

撰寫程式碼實務

下列方針會建議撰寫有效程式碼的方法。

  • 不要依賴程式碼中的例外狀況:例外狀況會大幅降低效能,所以應避免使用它們做為控制一般程式流程的方法。如果可以用撰寫程式碼的方式偵測到造成例外狀況的條件,那就這麼做,而不要攔截例外狀況本身,並處理該狀況。以程式碼進行偵測的一般案例包括 null 的檢查、將值指派給即將剖析為數值的 String,或是在套用算術運算之前檢查特定值。下列範例示範可能會造成例外狀況的程式碼和測試條件的程式碼。兩者都產生相同結果。

    // This is not recommended.
    try {
       result = 100 / num;
    }
    catch (Exception e) {
      result = 0;
    }
    
    // This is preferred.
    if (num != 0)
       result = 100 / num;
    else
      result = 0;
    
    ' This is not recommended.
    Try
       result = 100 / num
    Catch (e As Exception)
      result = 0
    End Try
    
    ' This is preferred.
    If Not (num = 0)
       result = 100 / num
    Else
      result = 0
    End If
    
  • 以 Managed 程式碼重寫呼叫密集的 COM 元件:.NET Framework 會提供簡單的方法,與傳統 COM 元件相互操作。好處是保留 COM 元件中現有資產的同時,還可以利用 .NET 的功能。但是在有些情況下,保留舊元件的效能成本使得將元件轉換 (Migrate) 為 Managed 程式碼變得較為值得。一切情況都是唯一的,而決定您是否需要移植元件的最好方式為針對您的網站執行效能度量。建議您檢查是否要將任何經常呼叫的 COM 元件移植至 Managed 程式碼。

    在許多情況下,將舊元件轉換為 Managed 程式碼是不可能的,特別是剛開始轉換 Web 應用程式時。在這類情況中,其中一個最大的效能阻礙是將資料從 Unmanaged 環境封送處理到 Managed 環境。因此,在相互操作時,請在一端或另一端盡可能執行許多工作,再接著進行單一呼叫,而不是進行一連串較小的呼叫。例如,Common Language Runtime 中所有字串都是 Unicode,所以您應該在呼叫 Managed 程式碼前,先將元件中的任何字串都轉換為 Unicode。

    任何 COM 物件或原生資源在完成處理時,都應立即釋放。這樣可讓其他要求使用它們,並將要求記憶體回收行程稍後釋放它們的相關效能問題減到最少。

  • 避免單一執行緒 Apartment (STA) COM 元件:根據預設,ASP.NET 不允許 STA COM 元件在網頁中執行。若要執行這些元件,您必須在 .aspx 檔案的 @ Page 指示詞中包含 ASPCompat=true 屬性。這樣會將網頁執行所用的執行緒集區切換到 STA 執行緒集區,同時也讓 HttpContext 和其他內建物件可以為 COM 物件使用。避免 STA COM 元件是效能最佳化,因為它避免從多執行緒 Apartment (Multithreaded Apartment,MTA) 到 STA 執行緒的任何呼叫封送處理 (Marshaling)。

    如果必須使用 STA COM 元件,請避免在執行期間進行多個呼叫,並試著在每個呼叫期間盡可能傳送許多資訊。也請避免在建構網頁期間建立 STA COM 元件。例如,在下列程式碼中,SampleSTAComponent 會在網頁建構時間具現化 (Instantiated),這是從非 STA 執行緒的執行緒所建立,而且會執行網頁。這對效能會有嚴重的影響,因為它需要在 MTA 與 STA 執行緒間進行封送處理,以建構網頁。

    <%@ Page Language="VB" ASPCompat="true" %>
    <script runat=server>
    Dim myComp as new SampleSTAComponent()
    Public Sub Page_Load()
        myComp.Name = "Sample"
    End Sub
    </script>
    <html>
    <%
        Response.Write(Server.HtmlEncode(myComp.SayHello))
    %>
    </html>
    

    慣用的機制是延遲建立物件,直到程式碼在 STA 執行緒下執行時 (如下列範例所示):

    <%@ Page Language="VB" ASPCompat="true" %>
    <script runat=server>
    Dim myComp
    Public Sub Page_Load()
        myComp = new SampleSTAComponent()
        myComp.Name = "Sample"
    End Sub
    </script>
    <html>
    <%
        Response.Write(Server.HtmlEncode(myComp.SayHello))
    %>
    </html>
    

    建議的作法是只在需要時,或在 Page_Load 方法中建構 COM 元件和外部資源。

    您絕對不應將 STA COM 元件儲存於共用資源 (如快取或工作階段狀態) 中,除了建構這些元件的執行緒外,其他的所有執行緒也都可以存取它們。即使 STA 執行緒會呼叫 STA COM 元件,但是只有建構 STA COM 元件的執行緒才可以服務呼叫,因此需要封送處理對建立者執行緒的呼叫。這種封送處理可能會造成顯著的效能降低和穩定性問題。在這類情況下,請考慮將 COM 元件製作成 MTA COM 元件,或是以 Managed 程式碼重寫元件。

請參閱

概念

最佳化 ASP.NET 中的效能

監視 ASP.NET 應用程式效能

ASP.NET 的效能計數器

ASP.NET 快取