65.9K
CodeProject is changing. Read more.
Home

Practical .NET2 and C#2 - ASP.NET 2.0: Handling a page request on several threads

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.30/5 (5 votes)

Apr 28, 2006

3 min read

viewsIcon

47140

downloadIcon

202

ASP.NET 2.0: Handling a page request on several threads.

Title Practical .NET2 and C#2
Author Patrick Smacchia
Publisher ParadoxalPress
Published January 2006
ISBN 0-9766132-2-0
Price USD 59.95 (ParadoxalPress price: USD 33.99)
Pages 896
Website www.PracticalDOT.NET (Browse and download the 647 listings and sample chapters)

Introduction

The execution of a request within the aspnet_wp.exe process is done on the same thread from end to end. For this, ASP.NET uses the I/O threads of the CLR’s thread pool, and can execute multiple requests simultaneously. In addition to avoiding a certain number of concurrency problems by design, this approach is generally more efficient as the reception of requests from a name pipe starts directly on an I/O thread. We then avoid the transmission of information between threads. Since there is no named pipes in IIS 6.0, things happen a little differently, and in this version, ASP.NET uses several worker threads of the CLR.

It happens that the processing of a request implies a long wait for the thread. Typically, this happens when we access a slow web service, or when we complete an expensive request on a database. In this case, the ASP.NET threading model is inefficient. In fact, the wait of a thread is not without consequences, since the number of threads in the pool used to serve the request can be limited. If most requests imply a wait from their threads, the server will quickly find itself overloaded. We are then faced with a design bottleneck.

ASP.NET 2.0 offers an infrastructure to solve this problem. The idea is that the finalization of the processing of a request be accomplished on a different thread than the one that initiated the processing. Meanwhile, the access to a resource, such as a web service or a database, is done without holding a thread from the pool. In the case of a SQL Server database access, this is possible using the asynchronous requests of ADO.NET 2.0. This feature allows making a request to a SQL Server database without using a thread from the pool while waiting for the data. The following example illustrates a page which uses the feature, allowing ASP.NET 2.0 to process a page on multiple threads, and the feature of ADO.NET 2.0 allowing asynchronous requests. The data recovered from the database is then presented in a GridView:

<%@ Page Language="C#" Async="true" 
         CodeFile="Default.aspx.cs" Inherits="_Default" %>
<html xmlns="http://www.w3.org/1999/xhtml" >
   <body>
      <form id="form1" runat="server">
         <asp:GridView ID="MyGridView" runat="server" 
                  AutoGenerateColumns="true" />
      </form>
   </body>
</html>
using System;
using System.Data;
using System.Data.SqlClient;
using System.Web;
using System.Web.UI;
using System.Threading;
public partial class _Default : System.Web.UI.Page {
   private SqlCommand cmd;
   private SqlConnection cnx;
   protected void Page_Load(object sender, EventArgs e) {
      Response.Write("PageLoad thread:"+ 
           Thread.CurrentThread.ManagedThreadId + 
           "<br/>");
      PageAsyncTask task = new PageAsyncTask( BeginInvoke, 
             EndInvoke, EndTimeOutInvoke, null);
      Page.RegisterAsyncTask(task);
   }
   public IAsyncResult BeginInvoke( object sender, 
                                    EventArgs e, 
                                    AsyncCallback callBack, 
                                    object extraData) {
      Response.Write( "BeginInvoke thread:" + 
         Thread.CurrentThread.ManagedThreadId + 
         "<br/>");
      cnx = new SqlConnection( "async=true ; server" + 
            " = localhost ; " + "uid=sa ; pwd =; " + 
            "database = ORGANIZATION");
      cmd = new SqlCommand( "SELECT * FROM EMPLOYEES", cnx );
      cnx.Open();
      return cmd.BeginExecuteReader( callBack, extraData, 
                         CommandBehavior.CloseConnection);
   }
   public void EndInvoke( IAsyncResult result ) {
      Response.Write( "EndInvoke thread:" + 
        Thread.CurrentThread.ManagedThreadId + "<br/>");
      SqlDataReader rdr = cmd.EndExecuteReader(result);
      if (rdr != null && rdr.HasRows) {
         MyGridView.DataSource = rdr;
         MyGridView.DataBind();
      }
      rdr.Close();
   }
   public void EndTimeOutInvoke( IAsyncResult result ) {
      if (cnx != null && cnx.State != ConnectionState.Closed) {
         cnx.Close();
      }
      Response.Write("TimeOut");
      Response.End();
   }
   protected override void OnPreRender( EventArgs e ) {
      Response.Write( "OnPreRender thread:" + 
        Thread.CurrentThread.ManagedThreadId + 
        "<br/>");
   }
   protected override void OnPreRenderComplete( EventArgs e ) {
      Response.Write( "OnPreRenderComplete thread:" + 
        Thread.CurrentThread.ManagedThreadId + 
        "<br/>");
   }
}

To support this multi-threaded processing model, you must set the Async property of the <%@ Page> directive to true. During the Load event, we can create instances of the System.Web.UI.PageAsyncTask class. Each instance represents a processing which will occur outside of the aspnet_wp.exe process. No thread from the pool is used to wait for the end of the processing. This processing is represented by three delegates:

  • A delegate to the method that will begin the processing of the request (i.e., the one which executes Page_Load()). In our example, the delegate references the BeginInvoke(). It is executed right after the PreRender() event.
  • A delegate to the method which completes the processing of the work on a thread of the pool once the work is completed. In our example, our delegate references the BeginInvoke method. It is executed right before the PreRenderComplete event.
  • A delegate to a method executed by a thread of the pool when the work was not completed within a certain amount of time. In this case, our delegate references the EndTimeOutInvoke() method.

The HTML page generated by this example starts as follows:

PageLoad thread:4
OnPreRender thread:4
BeginInvoke thread:4
EndInvoke thread:8
OnPreRenderComplete thread:8
...

If you wish to use this technique to asynchronously process a call to a web service, you can use the IAsyncResult BeginXXX() and EndXXX(IAsyncResult) methods of the proxy class generated by the wsdl.exe tool.