70-562 - Asynchronous Pages

Filed under: , , , , by:

Asynchronous pages in ASP.NET have been in ASP.NET since version 2.0, but I have noticed that very few developers take advantage of it. Now we have version 3.5 and soon 4.0 but still a lot of production ASP.NET applications don't use "async" pages. So due to that fact and the fact that 70-562 exam covers using asynchronous pages I decided to show how easy it is and encourage developers to learn it and use it.
To use async pages, one must add <%@ Page Async="true" %>. Then we add in Page.Load event either:

Page.AddOnPreRenderCompleteAsync(new BeginEventHandler(BeginMethod), 
new EndEventHandler(EndMethod));
or
Page.RegisterAsyncTask(new PageAsyncTask(new BeginEventHandler(BeginMethod),
new EndEventHandler(EndMethod),
new EndEventHandler(TimoutMethod),SomeArgumentToPass, true));
The first method can only register only 1 async task whereas the second one can register many async tasks and allows adding a timeout method. As one can see, it is not a rocket science, however a developer must know how to create asynchronous methods (BeginMethod and EndMethod) rather than synchronous methods (Method). As an example I created a simple page with a custom web control that reads rss weather feed from yahoo. In this control there are 4 methods: BeginFeed, EndFeed, TimeoutFeed and GetFeed (to test synchronous vs asynchronous). To read and parse rss feed I used System.Xml.Linq.XDocument object, even though since version 3.0 there is System.ServiceModel.Syndication.SyndicationFeed class that allows to read rss feeds - however that class doesn't expose asynchronous methods to load rss feed and in order to consume yahoo weather rss feed with that class, the Rss20FeedFormatter formatter would be necessary because of date / time format problems. Therefore, I used XDocument that fully meets our needs.
[ToolboxData("<{0}:WeatherWatcher runat=server></{0}:WeatherWatcher>")]
public class WeatherWatcher:WebControl 
{
private WebRequest _request;
private String _desc;
private String _name;
 
public IAsyncResult BeginFeed(object sender, EventArgs e, 
AsyncCallback cb,object state)
{
_request = WebRequest.Create(@"http://weather.yahooapis.com/forecastrss?p="+state.ToString());
return _request.BeginGetResponse(cb, state);
}
 
public void EndFeed(IAsyncResult ar)
{
using (WebResponse response = _request.EndGetResponse(ar))
{
using (StreamReader reader =
new StreamReader(response.GetResponseStream()))
{
XDocument doc = XDocument.Load(reader);
_name = doc.Element("rss").Element("channel").Element("item").Element("title").Value; 
_name=_name.Remove(_name.IndexOf("at"));
_desc = doc.Element("rss").Element("channel").Element("item").Element("description").Value;
_desc = _desc.Remove(_desc.IndexOf("<a"));
}
}
}
 
public void TimoutFeed(IAsyncResult ar)
{
_request.Abort();
_request = null;
_name = "";
_desc = "Couldn't retrieve weather information";
}
 
public void GetFeed(string zipcode)
{
XDocument doc = XDocument.Load(@"http://weather.yahooapis.com/forecastrss?p="+zipcode);
_name = doc.Element("rss").Element("channel").Element("item").Element("title").Value;
_name = _name.Remove(_name.IndexOf("at"));
_desc = doc.Element("rss").Element("channel").Element("item").Element("description").Value;
_desc = _desc.Remove(_desc.IndexOf("<a"));
}
 
protected override void RenderContents(HtmlTextWriter writer)
{
writer.RenderBeginTag(HtmlTextWriterTag.Center);
writer.RenderBeginTag(HtmlTextWriterTag.B);
writer.Write(_name ?? "Place name unavailable");
writer.RenderEndTag();
writer.RenderEndTag();
writer.Write(_desc ?? "Weather info  unavailable");
base.RenderContents(writer);
}
}
Now the page that will use our control. I have put it twice and each of them will show weather for different zip codes (Minneapolis and St. Paul) - Default.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="AsynPageTest.MainPage" Async="true" Theme="Theme" %>
<%@ Register assembly="WebControls" namespace="AsynPageTest" tagprefix="ww" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Weather Watcher</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<ww:WeatherWatcher ID="WeatherWatcher1" runat="server" />
<ww:WeatherWatcher ID="WeatherWatcher2" runat="server" />
</div>
</form>
</body>
</html>
Now the code-behind - Default.aspx.cs. It will contain 3 Page_Load only to demonstrate how to use all 3 ways to get feeds.
1. AddPreRenderCompleteAsync method
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
 
namespace AsynPageTest
{
public partial class MainPage : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
Page.AddOnPreRenderCompleteAsync(
new BeginEventHandler(WeatherWatcher1.BeginFeed), 
new EndEventHandler(WeatherWatcher1.EndFeed),"55401");
Page.AddOnPreRenderCompleteAsync(
new BeginEventHandler(WeatherWatcher2.BeginFeed), 
new EndEventHandler(WeatherWatcher2.EndFeed), "55101");
}
}
}
}
2. GetFeed - synchronous method
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
WeatherWatcher1.GetFeed("55401");
WeatherWatcher2.GetFeed("55101");
}
}
3. RegisterAsyncTask
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
Page.RegisterAsyncTask(new PageAsyncTask(
new BeginEventHandler(WeatherWatcher1.BeginFeed),
new EndEventHandler(WeatherWatcher1.EndFeed),
new EndEventHandler(WeatherWatcher1.TimoutFeed),
"55401", true));
Page.RegisterAsyncTask(new PageAsyncTask(
new BeginEventHandler(WeatherWatcher2.BeginFeed),
new EndEventHandler(WeatherWatcher2.EndFeed),
new EndEventHandler(WeatherWatcher2.TimoutFeed),
"55101", true));
}
}
Here is the screenshot of the page displaying our weather info.

I have also created a simple multithread test application (although I could've used WCAT - Web Capacity Analysis Tool) that was calling my pages using System.Net.WebClient class. However, before I started testing the performance I had to add a few more ASP.NET pages to our Web Application. The reason for that is that an asynchronous page does not load faster so testing 1 page would generate the same results whether we are using async or not. However, when using synchronous calls a thread from application pool is not released until it finishes processing a page (even if it has to wait for web service or database call or in our example a response with weather info), whereas asynchronous pages release processing thread to application pool for the time of waiting so that thread could be used to process other request in the meantime, which for multi pages applications it can mean a lot. My test showed that for 800 calls with ratio 1:9 (1 calling our async page and 9 calling other pages) there was 1 second difference within 5 second test between a synchronous page and an asynchronous page, and that's like 20% better performance. One more note: AddOnPreRenderCompleteAsync method can be used more than once but registered tasks using that method will be executed one after one, so only RegisterAsyncTask method can register more than 1 task to be executed in parallel.
I hope this will encourage developers to use asynchronous pages more often and help all studying to 70-562 exam to review information on asynchronous pages. Of course with advent of Ajax now developers can build pages that all long-running tasks can be executed during asynchronous postbacks without posting back the whole page. Nevertheless, I think that a professional developer should know more than 1 way of getting to a solution so he can propose the best one under the given circumstances, and in my opinion that's what makes a developer professional - not only being able to solve a problem but being able to solve it using the best solution.

0 comments: