For server-based applications, asynchronous operations are fundamental to scalability. So important for web applications is it that Microsoft have made asynchronous HTTP handlers available since ASP.NET 1.0. An asynchronous HTTP handler is a component that implements the IHttpAsyncHandler interface. The IHttpAsyncHandler interface extends the more popular IHttpHandler interface by adding a pair of begin/end methods to process the request. Here’s a basic skeleton of an asynchronous HTTP handler.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public class SampleHandler : IHttpAsyncHandler { private WorkerDelegate _delegate; protected delegate void WorkerDelegate(HttpContext context); public IAsyncResult BeginProcessRequest( HttpContext context, AsyncCallback cb, object extraData) { _delegate = new WorkerDelegate(ProcessRequest); return _delegate.BeginInvoke(context, cb, extraData); } public void EndProcessRequest(IAsyncResult result) { _delegate.EndInvoke(result); } public bool IsReusable { get { return true; } } public void ProcessRequest(HttpContext context) { // Do the actual processing for the request : } } |
As you can see, except for the body of ProcessRequest, the code of the class SampleHandler is boilerplate code. Over the years, however, both ASP.NET Web Forms and ASP.NET MVC have come to provide their own facilities to make it simpler for developers to implement HTTP handlers asynchronously. In particular, ASP.NET MVC has provided asynchronous controllers since version 2. Asynchronous controllers are even easier to implement with the latest release-ASP.NET MVC 4-thanks to some additional syntactic sugar in the C# language.
Before we delve deeper into the implementation of asynchronous controllers in ASP.NET MVC, let’s pinpoint why asynchronous operations are critical in server-side ASP.NET.
It’s All About Site Performance
Everybody would agree that asynchronously-served requests are important for the performance of potentially-lengthy operations. Taking this point to the limit, Windows 8 is highly dependent upon asynchronous operations and that is the preferred way of coding in the newest platform.
What exactly does performance mean in this context? Is it about serving the page (or data) to the client in a faster way? Is it about keeping the user interface responsive while the request is going on? As surprising as it may sound, the main point of asynchronous ASP.NET requests is neither of these.
From the user’s perspective, it will take the same time to complete a request whether it runs synchronously or asynchronously. Suppose that a given request triggers a server-side process that takes, say, 30 seconds. Well, the request won’t take less than 30 seconds to complete regardless of you using a plain HTTP handler or an asynchronous handler. Moreover, it may even be that the async operation takes a bit longer to complete.
What about the other point-the more responsive user interface?
The user experience determined by using an async model shines in rich client environment such as desktop Windows or Windows Phone applications where the application remains responsive while the lengthy tasks progresses. In a web scenario, there’s no difference at all if you fully refresh the page. If you use Ajax, instead, then your page will be highly responsive anyway, whether the request is synchronous or not.
So what’s the real benefit of implementing asynchronous requests?
The benefits of asynchronous requests are entirely for the server environment. In ASP.NET, async HTTP handlers keep the server runtime much more responsive by guaranteeing that a larger number of threads are available at any time to serve requests. In the end, it won’t so much be the requests for long-running tasks that benefit from async handlers, but all other requests, for images, scripts, and other static and ASPX pages. In this way, the site remains highly responsive for all users and long-running requests will still take their time to complete.
Async Management of ASP.NET Threads
ASP.NET serves requests by assigning them to threads picked up from the ASP.NET thread-pool. Requests are therefore served promptly as long as there are threads available. Suppose now that the site exposes several long-running operations and receives a lot of requests for these operations. Suppose, too, that each long-running request is implemented synchronously. This means that each long-running operation keeps a thread blocked for all the time it takes to complete the operation. With many of long-running operations pending, the number of free threads in the pool will diminish and requests will become more likely to be queued. Then, when the queue finally fills up, users start receiving error messages for unavailable server.
What’s exactly a long-running operation?
There are two types of long-running operations; the CPU-bound and the I/O-bound. A CPU-bound operation takes a while to complete because the CPU (or one of its cores) has a lot of work to do. Think for example of a complex statistic algorithm; the thread will take a while to return simply because it’s doing work. An I/O-bound operation on the other hand, is subject to I/O latency. In this case, the operation takes a while because its completion depends on data being received, or sent, to external devices. The running thread cannot return until the task is complete, but all it does is wait. A great example of an I/O-bound operation is invoking a web endpoint.
An Async programming model will efficiently solve any scalability problems that are caused by I/O-bound operations. A CPU-bound operation, on the other hand, is a true bottleneck: the operation can’t be avoided if you need its results but it keeps a thread blocked. To minimize the impact of CPU-bound operations you should try to transform it in a I/O-bound operation. This means that request be forwarded to a worker thread as it happens in code snippet we went through at the beginning of the article. Async programming therefore is essential. Just to give more the sense of importance of async programming, consider that, in Windows 8 and earlier in Windows Phone, Microsoft considers any operation to be a long-running operation that takes more than just a few milliseconds to complete.
Although it is only recently that Async programming has become publicised, the basic tools for async programming in ASP.NET have always been there. Let’s see how an async HTTP handler makes several I/O bound operations sustainable and thereby preserves scalability.
An async HTTP handler features two methods-BeginProcessRequest and EndProcessRequest. This means that each request executes in two non-consecutive steps. Each step requires its own ASP.NET thread. More often than not, the two steps of the same request are carried over by different ASP.NET threads. When the request comes in, one ASP.NET thread picks it up and executes it up until the end of BeginProcessRequest which is when you start the potentially lengthy operation such as invoking a remote web service. At this point, the original ASP.NET thread is released to the pool and made ready to serve another incoming request. Who does take care of waiting for the long-running operation to complete? As software is not magic, we do need some other thread to wait for that.
To deal with this, ASP.NET comes with two thread pools-the popular ASP.NET thread pool and the less known completion port thread pool (CPTP). Threads in the first pool manage incoming requests; threads in the second pool just manage the wait for I/O-bound operations. In the end, when an async operation ends the first step then ASP.NET performs a sort of thread switch: It picks up a thread from the CPTP and makes it wait. The completion of the long-running operation is internally associated with a callback that, once signaled, adds a new fake request to the ASP.NET queue to complete the previous request. At that point, the first free thread in the ASP.NET pool will pick it up and runEndProcessRequest. (See Figure 1.)
Figure 1. Async HTTP request processing in ASP.NET.
In ASP.NET Web Forms, this machinery is hidden inside asynchronous pages; in ASP.NET MVC, instead, you have theAsyncController class.
The AsyncController Class
In ASP.NET MVC, you start implementing asynchronous operations for potentially lengthy tasks by deriving your controller class from AsyncController. Note that an AsyncController class can serve both synchronous and asynchronous requests. The name of the method conventionally indicates how the method has to be processed by the runtime. An asynchronous action is implemented using a pair of methods aptly named to clearly indicate begin and completion steps. For example, a method GetLatestNews is implemented asynchronously as below:
1 2 3 4 5 6 7 8 9 10 11 |
public class SampleAsyncController : AsyncController { public void GetLatestNewsAsync(ModelBindingData data) { ... } public ActionResult GetLatestNewsCompleted(LongRunningTaskResponse data) { ... } } |
The xxxAsync method represents the beginning of the request-processing and it ends by triggering the long-running process. The xxxCompleted method represents the completion of the operation when data from the long-running process has been acquired, and it just remains to finalize the ASP.NET request. It is not a coincidence that xxxAsync is void whereas xxxCompleted returns anActionResult object. Also, note that model binding takes place on the xxxAsyncmethod, whereas xxxCompletedreceives any data that is relevant for the completion of the request.
It is important to note that if another method named xxx exists in the same controller class, and is not disambiguated by using HTTP verb or action name attributes, then an exception is thrown. For example, the following code won’t work:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class SampleAsyncController : AsyncController { // This is method "GetLatestNews" run synchronous public ActionResult GetLatestNews(ModelBindingData data) { ... } // This is method "GetLatestNews" run asynchronously public void GetLatestNewsAsync(ModelBindingData data) { ... } public ActionResult GetLatestNewsCompleted(LongRunningTaskResponse data) { ... } } |
Let’s see more details about the code you might want to have within xxxAsync and xxxCompleted.
Async Actions
The implementation of xxxAsync and xxxCompleted requires a bit of care as there are some common steps to be accomplished. In xxxAsync you need, first and foremost, to increase the count of pending asynchronous operations. Next, you add the code, being careful to use a method from the .NET Framework classes designed for async behavior. For example, if you need to make a HTTP server side request you use DownloadStringAsync from the WebClient class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public void GetLatestNewsAsync(SomeData data) { AsyncManager.OutstandingOperations.Increment(); // Do some remote work (for example, invoke a service) var client = new WebClient(); client.DownloadStringCompleted += (sender, e) => { // Raw data downloaded var data = e.Result; // Package data for the "completed" method AsyncManager.Parameters["RawResponse"] = data; ... // Terminate operations, and prepare data for the "completed" method AsyncManager.OutstandingOperations.Decrement(); }; client.DownloadStringAsync(uri); } |
Finally, you decrement the count of pending operations when you’re done. Any data you downloaded should, reasonably, be passed to the xxxCompleted method to be packaged into a view model or a JSON string and returned to the browser. You use the Parameters collection of the AsyncManager object to do this. Here is a possible implementation of the xxxCompleted method:
1 2 3 4 5 6 |
public ActionResult GetLatestNewsCompleted(String rawResponse) { return String.IsNullOrEmpty(rawResponse) ? (ActionResult)new HttpNotFoundResult() : Json(rawResponse, JsonRequestBehavior.AllowGet); } |
Note that parameters in the signature of xxxCompleted are subject to the same model binding rules as parameters received within the HTTP request.
Async Methods and Attributes
In ASP.NET MVC, you can apply several action filter attributes to controller methods. You should place any applicable attributes for an asynchronous method on the trigger method xxxAsync. Note that attributes applied to the xxxCompleted method will be ignored. If an ActionName attribute is placed on xxxAsync to alias it, then the xxxCompleted method must be named after the xxx name, not the action name.
You can set a timeout on a per-controller or per-action basis by using the AsyncTimeout attribute. The duration is expressed in milliseconds and defaults to 30 seconds.
1 |
[AsyncTimeout(3000)] |
By default, all methods are subject to this timeout. If you do not want any timeout, you set that preference explicitly by using theNoAsyncTimeout attribute. No timeout is equivalent to setting the timeout to the value of System.Threading.Timeout.Infinite.
Note also that you can tweak the value of the Timeout property on the AsyncManager class. By doing so, you set a new global timeout value that applies to any calls unless it’s overridden by attributes at the controller or action level.
Summary
Async programming has always been there, but probably only a small subset of particularly smart developers really cared about it for years. When more and more developers started looking at async programming, it became clear that adjustments were required to the programming model. Async pages in Web Forms and async controllers in ASP.NET MVC aren’t that hard to deal with, but they definitely make async a bit annoying and subliminally spring developers to use it only when strictly required.
The recent release of the .NET Framework 4.5 and newest version of the C# language put asynchronous programming on the spotlight and new language features really make async programming nearly undistinguishable from classic synchronous programming. There are no more excuses; go async or sink!
Interested in how your async code performs? ANTS Performance Profiler has an async profiling mode to help you understand where applications spend their time while doing async work. Try it out now.
Load comments