Asynchronous Pages in ASP.NET 2.0

Posted by DreamBig | 12:54 AM

ASP.NET 2.0 is replete with new features ranging from declarative data binding and Master Pages to membership and role management services. But my vote for the coolest new feature goes to asynchronous pages, and here's why.
When ASP.NET receives a request for a page, it grabs a thread from a thread pool and assigns that request to the thread. A normal, or synchronous, page holds onto the thread for the duration of the request, preventing the thread from being used to process other requests. If a synchronous request becomes I/O bound—for example, if it calls out to a remote Web service or queries a remote database and waits for the call to come back—then the thread assigned to the request is stuck doing nothing until the call returns. That impedes scalability because the thread pool has a finite number of threads available. If all request-processing threads are blocked waiting for I/O operations to complete, additional requests get queued up waiting for threads to be free. At best, throughput decreases because requests wait longer to be processed. At worst, the queue fills up and ASP.NET fails subsequent requests with 503 "Server Unavailable" errors.
Asynchronous pages offer a neat solution


to the problems caused by I/O-bound requests. Page processing begins on a thread-pool thread, but that thread is returned to the thread pool once an asynchronous I/O operation begins in response to a signal from ASP.NET. When the operation completes, ASP.NET grabs another thread from the thread pool and finishes processing the request. Scalability increases because thread-pool threads are used more efficiently. Threads that would otherwise be stuck waiting for I/O to complete can now be used to service other requests. The direct beneficiaries are requests that don't perform lengthy I/O operations and can therefore get in and out of the pipeline quickly. Long waits to get into the pipeline have a disproportionately negative impact on the performance of such requests.
The ASP.NET 2.0 Beta 2 async page infrastructure suffers from scant documentation. Let's fix that by surveying the landscape of async pages. Keep in mind that this column was developed with beta releases of ASP.NET 2.0 and the .NET Framework 2.0.

Asynchronous Pages in ASP.NET 1.x
ASP.NET 1.x doesn't support asynchronous pages per se, but it's possible to build them with a pinch of tenacity and a dash of ingenuity. For an excellent overview, see Fritz Onion's article entitled "Use Threads and Build Asynchronous Handlers in Your Server-Side Web Code" in the June 2003 issue of MSDN®Magazine.
The trick here is to implement IHttpAsyncHandler in a page's codebehind class, prompting ASP.NET to process requests not by calling the page's IHttpHandler.ProcessRequest method, but by calling IHttpAsyncHandler.BeginProcessRequest instead. Your BeginProcessRequest implementation can then launch another thread. That thread calls base.ProcessRequest, causing the page to undergo its normal request-processing lifecycle (complete with events such as Load and Render) but on a non-threadpool thread. Meanwhile, BeginProcessRequest returns immediately after launching the new thread, allowing the thread that's executing BeginProcessRequest to return to the thread pool.
That's the basic idea, but the devil's in the details. Among other things, you need to implement IAsyncResult and return it from BeginProcessRequest. That typically means creating a ManualResetEvent object and signaling it when ProcessRequest returns in the background thread. In addition, you have to provide the thread that calls base.ProcessRequest. Unfortunately, most of the conventional techniques for moving work to background threads, including Thread.Start, ThreadPool.QueueUserWorkItem, and asynchronous delegates, are counterproductive in ASP.NET applications because they either steal threads from the thread pool or risk unconstrained thread growth. A proper asynchronous page implementation uses a custom thread pool, and custom thread pool classes are not trivial to write (for more information, see the .NET Matters column in the February 2005 issue of MSDN Magazine).
The bottom line is that building async pages in ASP.NET 1.x isn't impossible, but it is tedious. And after doing it once or twice, you can't help but think that there has to be a better way. Today there is—ASP.NET 2.0.

Asynchronous Pages in ASP.NET 2.0
ASP.NET 2.0 vastly simplifies the way you build asynchronous pages. You begin by including an Async="true" attribute in the page's @ Page directive, like so: <%@ Page Async="true" ... %>
Under the hood, this tells ASP.NET to implement IHttpAsyncHandler in the page. Next, you call the new Page.AddOnPreRenderCompleteAsync method early in the page's lifetime (for example, in Page_Load) to register a Begin method and an End method, as shown in the following code: AddOnPreRenderCompleteAsync (
new BeginEventHandler(MyBeginMethod),
new EndEventHandler (MyEndMethod)
);
What happens next is the interesting part. The page undergoes its normal processing lifecycle until shortly after the PreRender event fires. Then ASP.NET calls the Begin method that you registered using AddOnPreRenderCompleteAsync. The job of the Begin method is to launch an asynchronous operation such as a database query or Web service call and return immediately. At that point, the thread assigned to the request goes back to the thread pool. Furthermore, the Begin method returns an IAsyncResult that lets ASP.NET determine when the asynchronous operation has completed, at which point ASP.NET extracts a thread from the thread pool and calls your End method. After End returns, ASP.NET executes the remaining portion of the page's lifecycle, which includes the rendering phase. Between the time Begin returns and End gets called, the request-processing thread is free to service other requests, and until End is called, rendering is delayed. And because version 2.0 of the .NET Framework offers a variety of ways to perform asynchronous operations, you frequently don't even have to implement IAsyncResult. Instead, the Framework implements it for you.
The codebehind class in Figure 1 provides an example. The corresponding page contains a Label control whose ID is "Output". The page uses the System.Net.HttpWebRequest class to fetch the contents of http://msdn.microsoft.com. Then it parses the returned HTML and writes out to the Label control a list of all the HREF targets it finds.
Since an HTTP request can take a long time to return, AsyncPage.aspx.cs performs its processing asynchronously. It registers Begin and End methods in Page_Load, and in the Begin method, it calls HttpWebRequest.BeginGetResponse to launch an asynchronous HTTP request. BeginAsyncOperation returns to ASP.NET the IAsyncResult returned by BeginGetResponse, resulting in ASP.NET calling EndAsyncOperation when the HTTP request completes. EndAsyncOperation, in turn, parses the content and writes the results to the Label control, after which rendering occurs and an HTTP response goes back to the browser.

When a synchronous page is requested, ASP.NET assigns the request a thread from the thread pool and executes the page on that thread. If the request pauses to perform an I/O operation, the thread is tied up until the operation completes and the page lifecycle can be completed. An asychronous page, by contrast, executes as normal through the PreRender event. Then the Begin method that's registered using AddOnPreRenderCompleteAsync is called, after which the request-processing thread goes back to the thread pool. Begin launches an asynchronous I/O operation, and when the operation completes, ASP.NET grabs another thread from the thread pool and calls the End method and executes the remainder of the page's lifecycle on that thread.

Read More...

ASP.NET 2.0 Session State Partitioning

Posted by DreamBig | 9:42 PM

SqlSessionStateStore supports a key scalability feature of ASP.NET 2.0 known as session state partitioning. By default, all sessions for all applications are stored in a single SQL Server database. However, developers can implement custom partition resolvers—classes that implement the IPartitionResolver interface—to partition sessions into multiple databases. Partition resolvers convert session IDs into database connection strings; before accessing the session state database, SqlSessionStateStore calls into the active partition resolver to get the connection string it needs. One use for custom partition resolvers is to divide session state for one application into two or more databases. Session state partitioning helps ASP.NET applications scale out horizontally, by eliminating the bottleneck of a single session state database. For an excellent overview of how partitioning works, and how to write custom partition resolvers, see "Fast, Scalable, and Secure Session State Management for Your Web Applications."


ASP.NET 2.0 provides a solution to the problems encountered when scaling up by enabling horizontal scale-out of session state stores through its state partitioning feature. State partitioning enables the session data and the associated processing load to be divided between multiple out-of-process state stores, allowing the session state load to scale as the Web farm grows and the number of concurrent sessions increases. It works by supplying a custom partitioning algorithm to SessionStateModule, which uses the algorithm to determine the state store connection string to be used for the current request based on the session ID. Both the SQLServer and the StateServer providers will then use the appropriate connection string to fetch and save the session.

You can implement a partitioning scheme by deriving a class from the System.Web.IPartitionResolver interface, and building the session ID-to-connection string mapping inside the ResolvePartition method. The basic implementation shown in Figure 5 creates an array of hardcoded connection strings corresponding to available state store partitions in the Initialize method. In the ResolvePartition method, the resolver hashes the session ID string into one of the buckets corresponding to one of the loaded connection strings, and selects the resulting connection string.

Ideally, you will want to implement either a configuration collection for specifying the available partitions that you will load in the Initialize method, or obtain the collection from a centralized location over the network on a Web farm. The simple uniform hashing implementation results in a relatively even distribution of sessions to stores over time because the session IDs are generated randomly. However, you may want to implement a load-balancing scheme where you dynamically determine the partition in which to place a given session based on current partition load. To do this, you will need to encode the partition ID into the session ID by using a custom SessionIDManager derivation together with the PartitionResolver to determine the partition for a new session, create the session ID with the partition ID encoded, and then determine the partition ID in future requests by pulling it out from the session ID in the partition resolver.
The partition resolver implementation can be deployed in the App_Code application directory, or it can be compiled into an assembly and deployed in the \Bin application directory or installed into the GAC. Finally, the resolver type has to be added to the session state configuration by specifying its fully qualified name in the partitionResolverType attribute.

Note that the partition resolver can only be used when session state is using the SQLServer or StateServer modes, and no connection string can be specified using the sqlConnectionString or stateConnectionString attributes.

Session state also provides an alternative approach for Web farm session state management, which allows the application to harness the speed of distributed InProc state storage (or out-of-process state storage for reliability purposes) provided that a session ID-encompassing affinity scheme can be used on the Web farm. The affinity scheme needs to ensure that all requests with a given session ID are passed to the same Web server, in which case each Web server can maintain its own session state store without sharing it with other Web servers.

The affinity scheme needs to be based on session IDs or other characteristics of the request that guarantee all requests containing a given session ID will be directed to the same Web server. Such schemes can be based on client IP network ranges (keeping in mind that clients may be coming from dynamically assigned Web proxies) or user agent headers. The problems with implementing such affinity schemes include the fact that they are not readily available on hardware load-balancing systems as they require HTTP-level routing of the requests (as opposed to the more common IP or TCP-level connection routing). In addition, these schemes prevent you from doing any real load balancing because routing needs to be deterministic with respect to session ID to state store mappings to preserve state.

Figure 5 Session Partitioning

public class PartitionResolver : System.Web.IPartitionResolver
{
private String[] partitions;

public void Initialize()
{
// create the partition connection string table
partitions = new String[] {
"tcpip=192.168.1.1:42424",
"tcpip=192.168.1.2:42424",
"tcpip=192.168.1.3:42424" };
}
public String ResolvePartition(Object key)
{
String sid = (String)key;
// hash the incoming session ID into
// one of the available partitions
int partitionID = Math.Abs(sid.GetHashCode()) % partitions.Length;
return partitions[partitionID];
}
}

Read More...