I finally had a chance to get back to looking at the new asynchronous page feature coming in 2.0, and contrary to my initial impression, I'm now quite pleased with the implementation. My
last post on the subject discussed what actually happens when you mark a page with the Async="true" attribute. What I have now tried, is actually building some asyncronous pages that perform lengthy web service invocations, and the results look pretty good. Here's the rundown:
First, I deployed a web service to a remote server that just slept for 3 seconds and returned a string.
Next, I wrote an .aspx page that invoked the webservice synchronously.
Then, I wrote an asynchronous .aspx page that invoked the webservice asyncronously as follows:
SlowWSAsync.aspx:
<%@ Page Language="C#" Async="true" CompileWith="SlowWSAsync.aspx.cs"
ClassName="PS.Samples.SlowWSAsync_aspx" %>
SlowWsAsync.aspx.cs:
using System;
namespace PS.Samples
{
public partial class SlowWSAsync_aspx
{
Slow slowWebservice = new Slow();
void Page_Load(object sender, EventArgs e)
{
BeginEventHandler bh = new BeginEventHandler(this.BeginGetAsyncData);
EndEventHandler eh = new EndEventHandler(this.EndGetAsyncData);
AddOnPreRenderCompleteAsync(bh, eh);
}
IAsyncResult BeginGetAsyncData(Object src, EventArgs args, AsyncCallback cb, Object state)
{
// Note - this is serviced on the same thread as Page_Load
// but a different thread is used to service EndGetAsyncData
//
return slowWebservice.BeginHelloWorld(cb, state);
}
void EndGetAsyncData(IAsyncResult ar)
{
string ret = slowWebservice.EndHelloWorld(ar);
Response.Write(AppDomain.GetCurrentThreadId());
}
}
}
Notice that it is now possible to write a page that is serviced asynchronous with respect to the request thread by setting Async='true' and registering two event handlers with 'AddOnPreRenderCompleteAsync'. The internal ProcessRequest implemenation of the page class will execute up to the PreRender event and then invoke the 'BeginEventHandler' if you have registered one. When the IAsyncResult returned from the BeginEventHandler (in my case named BeginGetAsyncData) is signaled because the asynchronous operation is complete, it completes the page lifecycle by rendering and then unloading the page.
The beauty of this model, is you can still write your page as you normally would, and just spin off and perform an asynchronous operation before the page is rendered and the response is returned. You of course need to be very careful of how you do this asynchronous work, but if you're making a web service invocation, the work is done for you, as the WebRequest class underneath the web service proxy will use Async I/O to perform the request, exactly what you want for this situation. Invoking a web service synchronously blocks the request thread performing no useful work while it awaits the result of the web service invocation. Shifting to this asynchronous model means that the request thread is freed up to service other requests while you await the return of the web service invocation. If you aren't performing a web request, keep in mind that launching an asynchronous delegate here will not help in relieving pressure on the threadpool since it draws from the same threadpool as that the request threads are drawn from.
To test my asynchronous page, I set up a test harness that invoked 200 concurrent requests to the web server from a separate client machine. 100 of the requests were made to a trivial page called fast.aspx, and 100 requests were made to either the synchronous or asynchronous page that invoked the web service. The idea was to see if I could clog the threadpool to impede request times for fast.aspx with slow web service requests, and then fix the problem by invoking the web service asynchronously with the async page mechanism. Here are the results:
Sync slow page and fast page 100 requests each
total requests issued: 100
distinct threads used: 33
average request time : 11.511252368
total requests issued: 100
distinct threads used: 33
average request time : 18.876843568
Async slow page and fast page 100 requests each
total requests issued: 100
distinct threads used: 2
average request time : 17.448289408
total requests issued: 100
distinct threads used: 2
average request time : 0.0636915840000001
My test harness records both the time each request takes, as well as the server-side thread id used to service the request thread (to determine how many distinct threads were used to service requests). As you can see, in the first test, the fast.aspx requests averaged over 11 seconds even though the page was utterly trivial, and the CPU was idle - the threadpool was clogged with long running pages performing synchronous web service requests. The second test used the asynchronous page, and as you can see, relieved pressure on the threadpool nicely so that the average fast.aspx page was serviced in 0.06 seconds. It also reduced the overall number of threads used to 2 because the load on the threadpool was reduced significantly.
So, the simplicity of the new asynchronous page model combined with asynchronous web service invocation in ASP.NET 2.0 creates a pretty compelling model for invocation - especially in this increasingly service-oriented world of ours :)
Posted
Oct 19 2004, 02:44 PM
by
fritz-onion