to download the source code please click here
In the earlier post we have discussed little bit about the limited number of worker threads available in the thread pool of ASP.NET and we identified why we should always try to avoid to block those important worker threads. ASP.NET 2.0 introduces Asyn=”true” attribute in the page to handle asynchronous scenarios and under the hood this attribute tells ASP.NET to implement IHttpAsyncHandler in the page. There are more than one ways available in ASP.NET 2.0 and ASP.NET 2.0 (plus) to register asynchronous tasks in the page,
- by AddOnPreRenderCompleteAsync method.
- by declaring PageAsynTask tasks and register them by RegisterAsyncTask method.
However PageAsyncTask provides some extra flexibility over AddOnPreRenderCompleteAsync method, for example
- it offers a timeout option.
- allows to register more than one async task.
- allows to configure async tasks to be executed in parallel or sequentially
- RegisterAsyncTask passes HttpContext.Current to the End and Timeout methods
- RegisterAsyncTask allows to pass object state to the Begin methods.
Lets look at how we can implement an asynchronous page to serve our scenario that we are focusing here, that is, to run long tasks asynchronously and at the same time provide immediate feedback to the browser.
Fig: aspx page
Fig: code behind
Here in the above code block I have left the AddOnPreRenderCompleteAsync code commented which can be used alternative to PageAsyncTask object & RegisterAsyncTask method. I have registered a PageAsyncTask task using RegisterAsyncTask method. The “task” object is configured to point to – BeginAsyncOperation, EndAsyncOperation and TimeoutAsyncOperation functions. The BeginAsyncOperation does all the lengthy bits inside request.BeginProcessRequest() method and signals when completed, this executes the EndAsyncOperation method. However if the lengthy task is not completed within the timeout period, the TimeoutAsyncOperation executes. Lets look at the AsyncRequest class now.
The most important thing to note here in the above piece of code is, I spawn an additional custom thread by hand and I execute the lengthy bits i.e. the “Process” Method in the custom thread. The Process method simulates the lengthy task by putting the thread to sleep. However note that the Response.Flush() does the magic to notify the connected browser about the task state immediately. It is a good idea to always check the Response.IsClientConnected property we discussed this in length in one of our earlier post.
As soon as the lengthy task completes I call the CompleteRequest method of the requestState object.
The CompleteRequest singnals that the asynchronous task is complete and it invokes the the callback method if available. In this example the callback invokes EndAsyncOperation method that we have configured in our PageAsyncTask object.
You may be wondering why I had to spawn a custom thread to run the lengthy operation where we can simply use asynchronous delegate invocation. You must know that Delegate.Invoke consumes worker thread from the ASP.NET thread pool. Even though the page releases a worker thread into the pool but immediately that thread ( or another worker thread ) is again taken from the thread pool. Now this precious thread is occupied until the long task finishes hence you gain nothing rather add overhead of switching thread from the pool.
Fritz Onion in his popular article “Use Threads and Build Asynchronous Handlers in Your Server-Side Web Code” mentioned the same, “Asynchronous delegate invocation completely defeats the purpose of building an asynchronous handler because it draws from the same process-wide CLR thread pool that ASP.NET uses to service requests. While the primary request thread is indeed returned to the thread pool, another thread is taken out to perform the asynchronous delegate execution, which means that there is a net gain of zero threads for servicing additional requests, rendering the handler useless. You will run into the same problem if you use ThreadPool.QueueUserWorkItem to perform the asynchronous request processing because it also draws threads from the same process-wide CLR thread pool”.
Jeff Prosise also quoted him in his article by saying “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.”
He also warned about it in his conclusion “A final point to keep in mind as you build asynchronous pages is that you should not launch asynchronous operations that borrow from the same thread pool that ASP.NET uses. For example, calling ThreadPool.QueueUserWorkItem at a page's asynchronous point is counterproductive because that method draws from the thread pool, resulting in a net gain of zero threads for processing requests”.
After all these warnings if you were still implementing the frequently recommended asynchronous delegation invocation ( unfortunately, most of the google search result on this topic of asynchronous programming in ASP.NET will lead to an implementation of asynchronous delegation ) it would look something like this:
I do not want to discuss about delegate invocation because it completely defeats the purpose of implementing Asynchronous pages. I have done some load testing on the above two implementations and found the following result:
Fig: Load test result of asynchronous handlers with a delegate.
Fig: Load test result of asynchronous handler with custom thread.
Clearly we see that we gain significantly on asynchronous handler that uses custom thread in both cases,
- requests/sec_total and
- avg. Respons_Total.
I got distracted a bit from our original subject matter again, but it was worth doing this one. I have already discussed here how we can run long running task asynchronously and how to provide immediate feedback to the connected browser immediately. In this post and previous posts I was implementing solutions by taking advantage of ASP.NET page and web handlers however we can also give immediate feedback to clients using MS AJAX, JQuery, XMLHTTPRequest, IFrame etc. in ASP.NET pages. Lets look at them one by one.