Asynchronous Pages and Data


The model for processing of ASP.NET pages is, by default, synchronous in nature. This means that there is a fixed flow to the page, with each page being processed by a thread; the thread isn't released until processing is finished. There are a limited number of threads available in the server thread pool, and once a server runs out of threads you start getting the dreaded "server unavailable" error. Even before that though, you'll see a performance loss as tasks wait for threads and memory use increases. So to improve performance, you want to not only process pages as fast as possible, but also release threads back to the thread pool as soon as you can. You generally don't have any explicit control over when to release the thread, but asynchronous processing can free up threads from long-running pages.

ASP.NET 2.0 introduces an easy model for asynchronous pages, but it's really important to understand exactly what asynchronous means in the context of Web pages. When a request is received from a client browser, the entire page has to be processed before the page can be returned to the browser. Asynchronous pages do not allow you to deliver pages in stages; you can buffer the output, but that's not the same thing. You can't deliver part of a page, run a task asynchronously, and then send more data back to the client.

The page is processed top to bottom by the same thread. That's the important point, because a single thread is taken up for the request. If the page stalls, perhaps accessing remote data via a Web Service, then the thread cannot be released back to the server until the request is finished. If this happens on lots of requests, it could be very simple for the server to run out of threads. Asynchronous pages allow you to remove the reliance upon a single thread if your page has a potentially long-running task as part of its normal processing routine. You dictate which tasks are to be processed asynchronously, and after the PreRender event, the asynchronous task is started. The thread processing the request is released back to the thread pool, and the page request waits until processing can continue; this happens when the asynchronous task finishes. ASP.NET knows when the asynchronous task finishes (it registers a delegate when the task starts), so it can take a thread from the thread pool and resume processing the page.

The asynchronous process is not about speeding up the processing of an individual page, but of the entire site. More threads available in the thread pool means that more requests can be processed.

Asynchronous Pages

To turn a page from a synchronous one to an asynchronous one, you add the Async attribute to the Page directive.

<%@ Page Async="true" . . . %>


You then need to register the event handlers that will start the asynchronous operation, and process the results once finished. For this you use the AddOnPreRenderCompleteAsync method, as shown in Listing 5.18.

Listing 5.18. The Code Behind an Asynchronous Page

public partial class AsyncPage : System.Web.UI.Page {   void Page_Load (object sender, EventArgs e)   {     AddOnPreRenderCompleteAsync (       new BeginEventHandler(BeginAsyncOperation),       new EndEventHandler (EndAsyncOperation)     );   }   IAsyncResult BeginAsyncOperation (object sender, EventArgs e,     AsyncCallback cb, object state)   {     // start long running task   }   void EndAsyncOperation (IAsyncResult ar)   {     // process results of long running task   } }

The task you perform needs to support the asynchronous model, and needs to have Begin and End methods; you call the Begin method in BeginAsyncOperation and the End method in EndAsyncOperation. You don't have to use the Page_Load event for the asynchronous registration; any event will do. For example, consider a Web page that shows a list of customers in a grid, and on selection more details for the customer are shown. Suppose you wish to fetch some data from a Web site, either by a Web Service or an RSS feed, to show some customer-related detailsperhaps the weather in a customer's location, so that you know what sort of day it is when you call them, as seen in Figure 5.5.

Figure 5.5. Capturing weather information asynchronously


This example uses the weather information provided by the Yahoo! Weather RSS feed at http://xml.weather.yahoo.com/rss/. The code for this (see Listing 5.19) shows three methods. The first is the DataBound method for the DetailsView, which is where the asynchronous registration takes place. This is only done if the SelectedIndex of the grid is not -1; this is if a row has actually been selected, which protects against the case where the data binding takes place but there is no selected row to bind against (which is how the data source controls work).

The next method, BeginAsyncOperation, is the start of the asynchronous operation, where the postal code of the selected customer is extracted from the DetailsView. The postal code is then used to define the location for the weather retrieval query. A WebRequest object is used to create an HTTP request to the query location, and the GetBeginResponse method is calledthis starts the request asynchronously, and it's at this time that ASP.NET releases the thread back to the thread pool.

The third method, EndAsyncOperation, starts when the asynchronous operation tells ASP.NET it has finished. Here the WebRequest response is retrieved using a WebResponse object, and the data is read from that using a StreamReader. The StreamReader is passed into the ReadXml method of a DataSet, and the forecast table is bound to a DataList. There are several tables with different data in the XML returned, but the forecast one contains the three-day weather forecast.

Listing 5.19. Asynchronously Fetching Weather Information

protected void DetailsView1_DataBound(object sender, EventArgs e) {   if (GridView1.SelectedIndex != -1)   {     AddOnPreRenderCompleteAsync(       new BeginEventHandler(BeginAsyncOperation),       new EndEventHandler(EndAsyncOperation));   } } IAsyncResult BeginAsyncOperation(object sender, EventArgs e,   AsyncCallback cb, object state) {   string city = DetailsView1.Rows[6].Cells[1].Text;   string qry =     string.Format("http://xml.weather.yahoo.com/forecastrss?p={0}",     city);   _request = WebRequest.Create(qry);   return _request.BeginGetResponse(cb, state); } void EndAsyncOperation(IAsyncResult ar) {   using (WebResponse response = _request.EndGetResponse(ar))   {     using (StreamReader reader =       new StreamReader(response.GetResponseStream()))     {       DataSet ds = new DataSet();       ds.ReadXml(reader);       WeatherDisplay.DataSource = ds.Tables["forecast"];       WeatherDisplay.DataBind();     }   } }\

To display the weather, a simple DataList is used; as well as text details, the forecast data contains a code that represents the weather, and this is matched to an image in the getWeatherIcon method (see Listing 5.20).

While this example used WebRequest and WebResponse to perform the asynchronous data access, it would be equally simple to call a Web Service. You could also use this for a potentially long data operation, as seen in the example code in Listing 5.21. You can see that the pattern is exactly the same as for the previous example; it's just the action being performed that is different. Notice that Dispose is overridden to ensure that the connection object is disposed of as soon as it is not required. You can normally accomplish this with the using statement, but since the connection is required across multiple methods, that isn't possible, so disposal is done when the page is disposed of.

Listing 5.20. Binding to the Asynchronously Supplied Data

<asp:DataList  runat="server"   RepeatColumns="4" RepeatDirection="Horizontal" CellPadding="10">   <ItemTemplate>     <div style="font-weight:bold;">       <%#Eval("day")%> <%#Eval("date")%></div>     <br />     <img src='/books/1/268/1/html/2/<%# getWeatherIcon(DataBinder.Eval(         Container.DataItem, "code"))%>'       alt='<%#Eval("text", "Forecast image: {0}")%>>'       style="height:100px; width:100px;"/><br />     <%#Eval("text")%><br />     <br />     High: <%#Eval("high")%><br />     Low: <%#Eval("low")%>   </ItemTemplate> </asp:DataList>

Listing 5.21. Asynchronous Data Binding

private SqlConnection _conn; private SqlCommand _cmd; private SqlDataReader _rdr; protected void Page_Load(object sender, EventArgs e) {   if (!IsPostBack)   {     AddOnPreRenderCompleteAsync(       new BeginEventHandler(BeginAsyncOperation),       new EndEventHandler(EndAsyncOperation));   } } IAsyncResult BeginAsyncOperation (object sender, EventArgs e,   AsyncCallback cb, object state) {   string cstr = WebConfigurationManager.ConnectionStrings     ["NorthwindConnectionString"].ConnectionString;   _conn = new SqlConnection(cstr);   _conn.Open();   _cmd = new SqlCommand("long query", _conn);   return _cmd.BeginExecuteReader(cb, state); } void EndAsyncOperation(IAsyncResult ar) {   _rdr = _command.EndExecuteReader(ar);   GridView1.DataSource = _rdr;   GridView1.DataBind(); } public override void Dispose() {   if (_conn != null)     _conn.Close();   base.Dispose(); }

Notice that the SqlCommand uses BeginExecuteReader and EndExecuteReaderthese are the asynchronous equivalent of ExecuteReader. This standard pattern will work for any object that supports asynchronous operation but should only be used where the operation is potentially lengthy and could tie up the thread.

If you are using ASP.NET 2.0 Web Service Proxies, then the pattern can change, because these proxies automatically support asynchronous operation with completion notification. Along with your Web method, the proxy generator creates a methodAsync to start the method asynchronously, and a methodCompleted event, for notification when the method has finished. If configured, the ASP.NET page will wait until the method has completed before finishing the page processing. For example, consider a Northwind Web Service, with the method shown in Listing 5.22, which simply returns a DataSet of products.

Listing 5.22. A Simple Web Service Method

[WebMethod] public DataSet GetProducts() {   DataSet ds = new DataSet();   ds.Merge(ProductsDataLayer.ReadCached());   return ds; }

If you create a Web Service Reference to this, called localNorthwindService, the proxy will be generated for you, allowing it to be used in an asynchronous manner, as shown in Listing 5.23.

Listing 5.23. Calling a Web Service Asynchronously

public partial class ch05_AsynchronousWebServices :                        System.Web.UI.Page {   private localNorthwindService.NorthwindService _service;   protected void Page_Load(object sender, EventArgs e)   {     if (!IsPostBack)     {       _service = new localNorthwindService.NorthwindService();       _service.GetProductsCompleted +=         new localNorthwindService.               GetProductsCompletedEventHandler(                 _service_GetProductsCompleted);       _service.GetProductsAsync();     }   }   void _service_GetProductsCompleted(object sender,        localNorthwindService.GetProductsCompletedEventArgs e)   {     GridView1.DataSource = e.Result;     GridView1.DataBind();   }   public override void Dispose()   {     if (_service != null)       _service.Dispose();     base.Dispose();   } }

You can see that the pattern is different, and simpler. You first instantiate the Web Service and then set an event handler for the GetProductsCompleted event, which will be called when the Web Service call completes. Then the method is called asynchronously. In the completed event handler, the results from the Web Service are available as a property of one of the event parameters.

The advantage of this pattern over the BeginAsyncOperation and EndAsyncOperation is not only the cleaner code (which is more noticeable if you have several asynchronous Web Service calls), but the fact that it also allows impersonation, the culture, and the HTTP context to flow to the completed event handler.

Asynchronous Tasks

You've seen that there are two patterns for performing asynchronous tasks; one that uses AddOnPreRenderCompleteAsync, and one that uses asynchronous Web Service proxies. If you are not using Web Services, but need to have context information (such as impersonation or culture) flow with the asynchronous task, then the existing methods won't work. They also don't support any form of timeout so that you don't wait indefinitely; some tasks, such as the weather one shown earlier, aren't critical to the page operation, so it wouldn't be sensible for the page to fail just because the weather forecast wasn't available.

To solve these problems, you can use asynchronous tasks, which is similar in use to AddOnPreRenderCompleteAsync in that you have begin and end events. The difference lies in the way the asynchronous event is hooked up, as shown in Listing 5.24.

You can see that the begin and end methods are the same as in previous examples, but there is now a method for timeouts. All three methods are set up in a different way, this time by constructing a PageAsyncTask object, the constructor of which takes delegates to the asynchronous methods, and an object that is the context to flow through the events. The PageAsyncTask object is then registered using RegisterAsyncTask.



ASP. NET 2.0 Illustrated
ASP.NET 2.0 Illustrated
ISBN: 0321418344
EAN: 2147483647
Year: 2006
Pages: 147

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net