Programming Insights.... Asynchronous Http Handlers

by Jason Clark of Wintellect

The new Web Forms model for the .NET Framework gets plenty of attention in magazine and Web articles, and rightly so. However, ASP.Net also allows developers to easily write server-side modules called Http Handlers in any .NET Language, such as C#. Http handlers are much more akin to a good old ISAPI DLL running in IIS.

Instead of being an object-oriented, event-driven model for creating Http responses, the Http handler simply allows the developer to output any text into the response stream, and what the handler outputs is what the browser gets. This is a fairly low-level approach to writing active server-side code, and has great potential for applications that require the utmost in scale-up scalability.

First, let's take a brief look at regular Http handlers.

Synchronous Http Handlers

The following is an example of a simple synchronous Http handler.

<%@ WebHandler Language="C#" Class="AsyncHttpHandler"%>

using System;
using System.Web;

public class AsyncHttpHandler : IHttpHandler {
  public void ProcessRequest(HttpContext context) {
    context.Response.Write("<H1>This is an Http handler.</H1>");
  }

  public bool IsReusable {
    get { return true; }
  }
}

Listing 1—Sync.ashx, a synchronous Http handler.

Copy the text in Listing 1 into a file with an .ashx extension and copy it into a virtual root directory for IIS (on a machine where the .NET Framework is installed). Now point a browser to the Web location and file name. ASP.Net will compile the code the first time the resource is requested. Each successive time the file is requested, the ProcessRequest() method is called to generate an Http response to send back to the client. The code in Listing 1 is a basic Http handler; a flexible ASP.Net feature indeed (and easier to write than an ISAPI DLL). But its main drawback is that it is not capable of being called asynchronously by ASP.Net.

Shortly I will explain how to create a more scalable asynchronous Http handler, but first let's look at the reasons why synchronous handlers can be less than perfect.

Most active server code is IO-bound rather than CPU-bound. What this means is that in general the bottleneck in producing a response is usually some second-tier network communication, database operation, or file-IO. The code in Listing 1, by the way, is CPU bound, and therefore works fine as a synchronous handler. IO-bound server code is common compared to CPU-bound server code, however.

Imagine an IO-bound scenario where a client request comes in to an Http handler. Let's say that the thread in ASP.Net's thread pool that is used to call the ProcessRequest() method is thread 1. When the Http handler code makes an IO request, latency is almost certain to occur, and the OS puts thread 1 into an efficient wait-state (to wait for the IO to complete). When a thread processing a request enters a wait state, ASP.Net awakens (or creates) another thread from its pool (thread 2) to serve another client request while thread 1 is blocked. This is the problem with synchronous handlers!

When an IO-bound Http handler is under a heavy load, the relationship described between thread 1 and thread 2 continues to occur between thread 2 and thread 3, thread 3 and thread 4, and so on. This is inefficient! Why should thread 2 be used to initiate work for the second client, when a perfectly good thread (thread 1) is sitting idle waiting on IO? The idle thread would be best utilized by doing more active work while IO is pending. This is how asynchronous Http handlers work.

Asynchronous Http Handlers

Asynchronous Http handlers work similarly to regular http handlers, except that they are capable of serving many client requests with very few actual threads. In fact, depending on how religiously the handler-code uses asynchronous methods in its implementation, it is possible to peg the CPU(s) in a system using only a handful of threads while serving many times as many clients. This approach has excellent scale-up possibilities.

To implement an asynchronous Http handler, your class must derive from the IHttpAsyncHandler interface rather than the IHttpHandler interface as seen in Listing 1. But the interface used is not the only difference between synchronous and asynchronous Http handlers. The program flow is also a bit more difficult to design.

The tough part of implementing an asynchronous Http handler is working with the Asynchronous Programming Model. All IO requests that you make in the process of creating the http response should be done asynchronously by using Begin*() and End*() methods, rather than their synchronous equivalents. For example, if your handler needs to read bytes from a file it should call BeginRead()/EndRead() on the Stream object rather than simply calling the synchronous Read(). You should call BeginRead() such that it calls your code back when the read has completed; meanwhile, your thread can be returned to the pool so that it is not wasted on an idle block.

In addition to using asynchronous Begin*() and End*() methods exclusively for all of your "blocking" IO calls, your asynchronous handler must also implement asynchronous BeginProcessRequest() and EndProcessRequest() methods that are called asynchronously by ASP.Net itself. The BeginProcessRequest() and EndProcessRequest() are the IHttpAsyncHandler interface's methods that allow ASP.Net to do asynchronously what it would have done synchronously using IHttpHandler's ProcessRequest() method.

Here is a summary of the things that you will have to think about when creating an asynchronous Http handler.

  1. You should do all of your IO using asynchronous Begin*() and End*() methods.
  2. You must implement Begin*() and End*() methods that allow ASP.Net to call you asynchronously.
  3. You must create a type that implements IAsyncResult that you instantiate and return to ASP.Net from your Begin*() method. ASP.Net will pass the object back to your End*() method. You should use this object to keep track of client-request state (such as the context object) as the work is divided amongst multiple calls.

The code shown in Listing 2 is a very simple asynchronous http handler (which you can deploy in the same manner described for the code in Listing 1).

<%@ WebHandler Language="C#" Class="AsyncHttpHandler"%>

using System;
using System.Web;
using System.Threading;

public class AsyncHttpHandler : IHttpAsyncHandler {
  public IAsyncResult BeginProcessRequest(
    HttpContext context, AsyncCallback cb, Object extraData){
    // Create an async object to return to caller
    Async async = new Async(cb, extraData);
    // store a little context for us to use later as well
    async.context = context;

    // Normally real work would be done here... then, most likely
    // as the result of an async callback, you eventually
    // "complete" the async operation so that the caller knows to
    // call the EndProcessRequest() method
    async.SetCompleted();

    // return IAsyncResult object to caller
    return async;
  }
  
  // Finish up
  public void EndProcessRequest(IAsyncResult result){
    // Finish things
    Async async = result as Async;
    async.context.Response.Write(
      "<H1>This is an <i>Asynchronous</i> response!!</H1>");
  }

  // This method is never called by ASP.Net in the async case
  public void ProcessRequest(HttpContext context) {
    throw new InvalidOperationException(
              "ASP.Net should never use this method");
  }

  // This means that the same AsyncHttpHandler object is used
  // for all requests returning false hear makes ASP.Net create 
  //  object per request.
  public bool IsReusable {
    get { return true; }
  }
}

// This object is necessary for the caller to help your code keep
// track of state between begin and end calls
class Async:IAsyncResult{
  internal Async(AsyncCallback cb, Object extraData){
    this.cb = cb;
    asyncState = extraData;
    isCompleted = false;
  }

  private AsyncCallback cb = null;
  private Object asyncState;
  public object AsyncState {
    get {
      return asyncState;
    }
  }

  public bool CompletedSynchronously {
    get {
      return false;
    }
  }

  // If this object was not being used solely with ASP.Net this
  // method would need an implementation. ASP.Net never uses the
  // event, so it is not implemented here.
  public WaitHandle AsyncWaitHandle {
    get {
      throw new InvalidOperationException(
                "ASP.Net should never use this property");
    }
  }

  private Boolean isCompleted;
  public bool IsCompleted {
    get {
      return isCompleted;
    }
  }

  internal void SetCompleted(){ 
    isCompleted = true;
    if(cb != null){
       cb(this);
    }
  }

  // state internal fields
  internal HttpContext context=null;
}

Listing 2—Async.ashx, an asynchronous Http handler.

There is more to say about asynchronous server code, and if this article generates interest, perhaps I will publish a second part on this site in the future. Unfortunately, though, this article has already reached more than double its expected size for the Web site and I frankly hope they have space for this much! Fortunately, this coverage should get you well on your way to writing scalable server-side code using asynchronous Http handlers.

In future versions of the .NET Framework, look for more innovations in the asynchronous programming model. For example, eventually there will be an asynchronous Page object that provides a way to implement Web forms and other more structured Web applications asynchronously.

This is cool stuff. Have fun!

About the Author

Jason Clark has been banging code since he fell in love with computers way back in sixth grade. Since the early nineties, Jason professional life has been devoted to Windows development. His most recent full-time employment was with Microsoft, where he wrote security protocols for the Windows operating systems.

Jason now does software consulting and writes about a variety of topics ranging from developing secure software to writing software that runs on Microsoft's new .NET platform. Jason coauthored Programming Server-Side Applications for Microsoft Windows 2000, and he writes articles for Dr. Dobbs Journal, MSDN Magazine (formerly MSJ), Windows Developers Journal, and other leading developer magazines. Jason's commercial software credits include work he has performed for Microsoft, IBM, Sony, HP, and other companies, and involve everything from writing printer drivers to helping develop the Windows 2000 and "Whistler" operating systems.

# # #



Comments

  • Goal

    Posted by snareenactina on 11/05/2012 09:02pm

    So, don't try to increase your speed when you're climbing a hill - just maintain the same speed, or even allow the car to go a little slower. Heck, it's a hill! Your wallet will thank you. mcculloch Washington Post reporters or editors recommend this comment or reader post. armedand citreola gathas psychosoclal dete

    Reply
  • This code has problems

    Posted by MariaTverdostup on 05/09/2005 02:11pm

    If IAsyncResult::SetCompleted() is called right inside BeginProcessRequest() on the same thread then it actually is completed synchronously! You should start another thread for asynchronous execution.
    Or at least please return true from IAsyncResult::CompletedSynchronously() method. It is important because ASP.NET may easily crash if you don't tell the truth :)

    Reply
  • ssss

    Posted by Legacy on 02/12/2004 12:00am

    Originally posted by: abdc

    sdsfsdf

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds