任何編程模型都有常見的效能缺陷,ASP.NET 也不例外。本節描述一些可避免在代碼中出現效能瓶頸的方法。
- 在未使用時禁用工作階段狀態: 並非所有的應用程式或頁都要求基於每個使用者的工作階段狀態。如果不需要,可將其完全禁用。這可以通過以下頁層級指令輕鬆實現:
<%@ Page EnableSessionState="false" %>
注意:如果頁需要訪問會話變數但不建立或修改它們,請將指令值設定為 ReadOnly。還可為 XML Web 服務方法禁用工作階段狀態。請參閱 XML Web 服務一節中的使用對象和內部對象。
- 謹慎選擇工作階段狀態提供者: ASP.NET 為儲存應用程式的會話資料提供了三種不同的方法:進程內工作階段狀態、作為 Windows 服務的進程外工作階段狀態和 SQL 資料庫中的進程外工作階段狀態。每種方法都有自己的優點,但進程內工作階段狀態是迄今為止速度最快的解決方案。如果僅在工作階段狀態中儲存少量易失資料,則應使用進程內提供者。進程外解決方案主要用於 Web 花園和 Web 農場方案,或用於當伺服器/進程重新啟動時不能遺失資料的情況。
- 避免與伺服器間的過多往返行程:Web Form頁架構是 ASP.NET 的最佳功能之一,因為它可以顯著減少為完成某項任務所需編寫的代碼量。使用伺服器控制項和回傳事件處理模型的頁元素編程訪問無疑是最省時的功能。但是,對這些功能的使用存在著適當和不適當的方法,瞭解何時使用它們是適當的很重要。
應用程式通常僅在檢索資料或儲存資料時才需要往返於伺服器。多數資料操作可在往返行程間在用戶端進行。例如在使用者提交資料前,通常可以在用戶端驗證表單項。通常,如果不需要將資訊中繼回伺服器,則不應往返於伺服器。
如果編寫自己的伺服器控制項,請考慮讓它們為上級(支援 ECMAScript)瀏覽器呈現用戶端代碼。通過採用“智能”控制項,可顯著減少對 Web 服務器的不必要點擊次數。
- 使用 Page.IsPostback 避免往返行程上的額外工作:如果處理伺服器控制項回傳,通常需要在第一次請求頁時執行代碼,該代碼不同於激發事件時用於往返行程的代碼。如果檢查 Page.IsPostBack 屬性,則代碼可按條件執行,具體取決於是否有對頁的初始請求或對伺服器控制項事件的響應。這樣做似乎很明顯,但實際上可以忽略此項檢查而不更改頁的行為。例如:
<script language="C#" runat="server"> public DataSet ds; ... void Page_Load(Object sender, EventArgs e) { // ...set up a connection and command here... if (!Page.IsPostBack) { String query = "select * from Authors where FirstName like '%JUSTIN%'"; myCommand.Fill(ds, "Authors"); myDataGrid.DataBind(); } } void Button_Click(Object sender, EventArgs e) { String query = "select * from Authors where FirstName like '%BRAD%'"; myCommand.Fill(ds, "Authors"); myDataGrid.DataBind(); }</script><form runat="server"> <asp:datagrid datasource='<%# ds.DefaultView %>' runat="server"/><br> <asp:button onclick="Button_Click" runat="server"/></form> <script language="VB" runat="server"> Public ds As DataSet ... Sub Page_Load(sender As Object, e As EventArgs) ' ...set up a connection and command here... If Not (Page.IsPostBack) Dim query As String = "select * from Authors where FirstName like '%JUSTIN%'" myCommand.Fill(ds, "Authors") myDataGrid.DataBind() End If End Sub Sub Button_Click(sender As Object, e As EventArgs) Dim query As String = "select * from Authors where FirstName like '%BRAD%'" myCommand.Fill(ds, "Authors") myDataGrid.DataBind() End Sub</script><form runat="server"> <asp:datagrid datasource='<%# ds.Tables["Authors"].DefaultView %>' runat="server"/><br> <asp:button onclick="Button_Click" runat="server"/></form> <script language="JScript" runat="server"> public var ds:DataSet; ... function Page_Load(sender:Object, e:EventArgs) : void { // ...set up a connection and command here... if (!Page.IsPostBack) { var query:String = "select * from Authors where FirstName like '%JUSTIN%'"; myCommand.Fill(ds, "Authors"); myDataGrid.DataBind(); } } function Button_Click(sender:Object, e:EventArgs) : void { var query:String = "select * from Authors where FirstName like '%BRAD%'"; myCommand.Fill(ds, "Authors"); myDataGrid.DataBind(); }</script><form runat="server"> <asp:datagrid datasource='<%# ds.DefaultView %>' runat="server"/><br> <asp:button onclick="Button_Click" runat="server"/></form> |
C# |
VB |
JScript |
|
|
Page_Load
事件對所有請求都執行,因此檢查了 Page.IsPostBack,以便在處理 Button_Click
事件回傳時不執行第一個查詢。請注意,即使沒有此檢查,頁的行為也不會改變,因為第一個查詢中的綁定會被事件處理常式中的 DataBind 調用推翻。記住,在編寫頁時會很容易忽略這個簡單的效能改進。
- 謹慎適當地使用伺服器控制項:儘管伺服器控制項使用起來非常容易,但它並不總是最佳選擇。許多情況下,簡單的呈現或資料繫結替換可以完成同樣的事情。例如:
<script language="C#" runat="server"> public String imagePath; void Page_Load(Object sender, EventArgs e) { //...retrieve data for imagePath here... DataBind(); }</script><%-- the span and img server controls are unecessary...--%>The path to the image is: <span innerhtml='<%# imagePath %>' runat="server"/><br><img src='<%# imagePath %>' runat="server"/><br><br><%-- use databinding to substitute literals instead...--%>The path to the image is: <%# imagePath %><br><img src='<%# imagePath %>' /><br><br><%-- or a simple rendering expression...--%>The path to the image is: <%= imagePath %><br><img src='<%= imagePath %>' /> <script language="VB" runat="server"> Public imagePath As String Sub Page_Load(sender As Object, e As EventArgs) '...retrieve data for imagePath here... DataBind() End Sub</script><%--the span and img server controls are unecessary...--%>The path to the image is: <span innerhtml='<%# imagePath %>' runat="server"/><br><img src='<%# imagePath %>' runat="server"/><br><br><%-- use databinding to substitute literals instead...--%>The path to the image is: <%# imagePath %><br><img src='<%# imagePath %>' /><br><br><%-- or a simple rendering expression...--%>The path to the image is: <%= imagePath %><br><img src='<%= imagePath %>' /> <script language="JScript" runat="server"> public var imagePath:String; function Page_Load(sender:Object, e:EventArgs) : void { //...retrieve data for imagePath here... DataBind(); }</script><%-- the span and img server controls are unecessary...--%>The path to the image is: <span innerhtml='<%# imagePath %>' runat="server"/><br><img src='<%# imagePath %>' runat="server"/><br><br><%-- use databinding to substitute literals instead...--%>The path to the image is: <%# imagePath %><br><img src='<%# imagePath %>' /><br><br><%-- or a simple rendering expression...--%>The path to the image is: <%= imagePath %><br><img src='<%= imagePath %>' /> |
C# |
VB |
JScript |
|
|
在此樣本中,不需要伺服器控制項將值代入發送回用戶端的結果 HTML。在許多其他情況下此方法同樣適用,甚至在伺服器控制項範本中。但是,如果要以編程方式操作控制項屬性、從中處理事件或利用其狀態儲存,則伺服器控制項更合適。應檢查伺服器控制項的使用,並尋找可最佳化的代碼。
- 避免過多的伺服器控制項檢視狀態:自動狀態管理是一種功能,它使伺服器控制項能夠在往返行程中重新填充它們的值,而不要求編寫任何代碼。但是,此功能並不能任意使用,因為控制項狀態是在隱藏的表單欄位中傳入和傳出伺服器的。應當明白 ViewState 何時有協助,何時沒有。例如,如果在每個往返行程中將控制項綁定到資料(如第四條提示中的資料格樣本所示),則不要求控制項維護它的檢視狀態,因為無論如何都將擦除任何重新填充的資料。
預設情況下,為所有的伺服器控制項啟用 ViewState。若要禁用它,請將控制項的 EnableViewState 屬性設定為 false,如下例所示:
<asp:datagrid EnableViewState="false" datasource="..." runat="server"/>
還可在頁層級關閉 ViewState。這在根本不從頁回傳時非常有用,如下例所示:
<%@ Page EnableViewState="false" %>
注意,User Control 指令也支援此屬性。若要分析頁上的伺服器控制項使用的檢視狀態量,請啟用跟蹤並查看“控制項階層”表中的“檢視狀態”列。有關跟蹤功能及如何啟用它的更多資訊,請參閱應用程式層級的追蹤記錄功能。
- 對字串串連使用 Response.Write:在頁面或使用者控制項中對字串串連使用 HttpResponse.Write 方法。該方法提供非常有效緩衝和串連服務。但是,如果您打算執行大量的串連,則使用下列樣本中的方法(即多次調用 Response.Write)來連接字串比僅調用一次 Response.Write 方法要快。
Response.Write("a");Response.Write(myString);Response.Write("b");Response.Write(myObj.ToString());Response.Write("c");Response.Write(myString2);Response.Write("d"); Response.Write("a")Response.Write(myString)Response.Write("b")Response.Write(myObj.ToString())Response.Write("c")Response.Write(myString2)Response.Write("d") Response.Write("a");Response.Write(myString);Response.Write("b");Response.Write(myObj.ToString());Response.Write("c");Response.Write(myString2);Response.Write("d"); |
C# |
VB |
JScript |
|
|
- 不要依賴代碼中的異常:異常非常浪費資源,應在代碼中盡量避免。絕不要將異常作為控制常規程式流的方法。如果可以在代碼中檢測到會導致異常的條件,就應該那樣做,而不要等到捕捉異常後再處理該條件。常見的方案包括:檢查空值,分配給將分析為數字值的字串,或在應用數學運算前檢查特定值。例如:
// Consider changing this:try { result = 100 / num;}catch (Exception e) { result = 0;}// To this::if (num != 0) result = 100 / num;else result = 0; ' Consider changing this:Try result = 100 / numCatch (e As Exception) result = 0End Try// To this:If Not (num = 0) result = 100 / numElse result = 0End If // Consider changing this:try { result = 100 / num;}catch (e:Exception) { result = 0;}// To this:if (num != 0) result = 100 / num;else result = 0; |
C# |
VB |
JScript |
|
|
- 在 Visual Basic 或 JScript 代碼中使用早期繫結:Visual Basic、VBScript 和 JScript 的優點之一是它們無類型的特性。只需使用它們即可建立變數,並不需要顯式的型別宣告。從一種類型分配到另一種類型時,同樣會自動執行轉換。這既是優點也是缺點,因為就效能而言,晚期綁定雖很方便但很昂貴。
Visual Basic 語言現在通過使用特殊的 Option Strict 編譯器指令來支援型別安全編程。為了向後相容,預設情況下 ASP.NET 不啟用 Option Strict。但為了獲得最佳效能,應在頁上使用 Strict 屬性或使用 Control 指令,為頁啟用 Option Strict:
<%@ Page Language="VB" Strict="true" %><%Dim BDim C As String' This causes a compiler error:A = "Hello"' This causes a compiler error:B = "World"' This does not:C = "!!!!!!"' But this does:C = 0%>
JScript 也支援無類型編程,但它不提供強制早期繫結的編譯器指令。滿足以下條件的變數是後期綁定的:
- 被顯式聲明為對象。
- 是無型別宣告的類的欄位。
- 是無顯式型別宣告的專用函數/方法成員,並且無法從其使用推斷出類型。
最後一個特點非常複雜。如果 JScript 編譯器能夠根據使用變數的方式推斷出其類型就會進行最佳化。在下面的樣本中,變數 A 為早期繫結,而變數 B 為後期綁定:
var A;var B;A = "Hello";B = "World";B = 0;
為獲得最佳效能,請將 JScript 變數聲明為具有類型。例如,“var A : String”。
- 將大量調用的 COM 組件移植為Managed 程式碼:.NET 架構一種非常容易的方法與傳統的 COM 組件相互操作。其優點是可以在保留現有代碼的同時利用新的平台。但是,在某些情況下,保留舊組件的效能成本超出了將組件遷移到Managed 程式碼的費用。每種情況都非常特別,而決定所需更改的內容的最佳方法是測量網站的效能。但是,通常 COM 互動性的效能影響與函數調用的次數或從Unmanaged 程式碼封送到Managed 程式碼的資料量成比例。由於各層間的通訊數,需要同大量調用互動的組件稱為“chatty”。應考慮將這種組件移植為完全託管的代碼,以從 .NET 平台提供的效能收益中獲益。或者可以考慮重新設計組件,以請求更少的調用或一次封送更多資料。
- 將 SQL 預存程序用於資料訪問:在 .NET 架構提供的所有資料存取方法中,基於 SQL 的資料訪問是產生效能最好的可縮放 Web 應用程式的最佳選擇。使用託管 SQL 提供者時,可通過使用編譯的預存程序而不是特殊查詢,獲得額外的效能提高。有關使用 SQL 預存程序的樣本,請參考本教程的伺服器端的資料訪問一節。
- 使用 SqlDataReader 獲得快進唯讀資料遊標:SqlDataReader 對象對從 SQL 資料庫中檢索的資料提供前進唯讀遊標。如果 SqlDataReader 適合於您的情況,則它是一個比 DataSet 更好的選擇。因為 SqlDataReader 支援 IEnumerable 介面,甚至還可以綁定伺服器控制項。有關使用 SqlDataReader 的樣本,請參閱本教程的伺服器端資料訪問一節。
- 儘可能快取資料和輸出:ASP.NET 編程模型提供了一個簡單的機制,在不需要為每個請求動態計算頁輸出或資料時緩衝它們。在設計頁時可以考慮用緩衝來最佳化應用程式中那些預期有最大通訊量的地方。適當地使用緩衝可增強網站的效能,有時甚至可以增大一個數量級或更多,這是 .NET 架構的任何其他功能無法企及的。有關如何使用緩衝的更多資訊,請參閱本教程的快取服務一節。
- 為多處理器電腦啟用 Web 花園:ASP.NET 進程模型協助在多處理器電腦上啟用可縮放性,將工作分發給多個進程(每個 CPU 一個),並且每個進程都將處理器關係設定為其 CPU。該技術稱為 Web 園藝,它可以顯著提高某些應用程式的效能。若要瞭解如何啟用 Web 園藝,請參考使用進程模型一節。
- 不要忘記禁用偵錯模式:ASP.NET 配置中的 <compilation> 節控制應用程式是否在偵錯模式中編譯。偵錯模式嚴重降低效能。在部署生產應用程式或測量效能之前,始終記住禁用偵錯模式。有關偵錯模式的更多資訊,請參考題為 SDK 調試器的章節。