The Need for Asynchrony


A good rule of thumb to follow when you are considering introducing asynchrony in an application is that you should successfully convince at least four of your peers that it is a good idea before you actually take the step. Asynchronous operations generally make it much harder to understand application flow and can introduce subtle bugs that are difficult to track down. When used properly, however, they can also significantly increase performance and/or make the user interface much more responsive. This section outlines two of the most common reasons you might consider introducing asynchrony into your Web pages in ASP.NET: to exploit parallelism and to relax thread-pool contention.

Exploiting Parallelism

The first and most obvious case for introducing asynchrony is to parallelize tasks in a request. Instead of executing each task (like a database retrieval or a Web service invocation) sequentially, you can run all the tasks concurrently (as long as they are not interdependent) and reduce the overall time taken to respond to the client. Figure 9-1 shows a sample ASP.NET page that displays four different sets of data to the clientnews headlines, a set of stock quotes, a collection of sales reports, and a weather forecast. Assuming each of these data sets come from separate sources, this page is an ideal candidate for introducing parallelism.

Figure 9-1. Synchronous page performing multiple independent tasks


To make this example concrete, let's assume that the news headlines, stock quotes, and weather forecast are all retrieved from remote Web service calls, and that the sales report is generated from a SQL query to a database on the local network. Let's also assume that each data retrieval takes approximately the same (non-negligible) amount of time, which we achieve in our sample application by artificially sleeping for three seconds in each Web method implementation and by adding a delay of three seconds in our SQL query. Listing 9-1 shows the sample .aspx page source, and Listing 9-2 shows the corresponding codebehind that populates the controls.

Listing 9-1. SynchronousPage.aspx

[View full width]

<%@ Page Language="C#" AutoEventWireup="true"          CodeFile="SynchronousPage.aspx.cs"          Inherits="SynchronousPage" %> <!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>Synchronous Page</title> </head> <body>     <form  runat="server">     <div>         <h1>Synchronous Page</h1>        <table border="1" cellpadding="0" cellspacing="0"               style="width:100%;height:500px" >             <tr>                 <td valign="top">                     <h4>News Headlines</h4>                     <asp:BulletedList                                        runat="server" />                 </td>                 <td valign="top">                     <h4>Stock Quotes</h4>                     <asp:GridView runat="server"  />                 </td>             </tr>             <tr>                 <td valign="top">                     <h4>Sales Report</h4>                     Sales data for                     <asp:TextBox  runat="server"                                  Width="120px">2005</asp:TextBox>                     <asp:Button  runat="server"                                 Text="Set year" /><br />                     <asp:GridView  runat="server"                                  AutoGenerateColumns="False"                                  DataKeyNames="id">                        <Columns>                            <asp:BoundField DataField="id"                                            HeaderText="id"                                 Visible="False" ReadOnly="True" />                            <asp:BoundField DataField="quarter"                                            HeaderText="Quarter" />                            <asp:BoundField DataField="amount"                                            HeaderText="Amount"                                            DataFormatString="{0:c}"                                            HtmlEncode="False" />                            <asp:BoundField DataField="projected"                                            HeaderText="Projected"                                            DataFormatString="{0:c}"                                            HtmlEncode="False" />                        </Columns>                    </asp:GridView>                    <asp:Label runat="server"                            ForeColor="red" EnableViewState="false" />                </td>                <td valign="top">                    <h4>Weather Forecast</h4>                    Weather forecast for zipcode: 04090 <br />                    <asp:Label Font-Bold="true" runat="server"                                />                </td>            </tr>        </table>    </div>    </form> </body> </html> 

Listing 9-2. SynchronousPage.aspx.cs

public partial class SynchronousPage : Page {   protected void Page_Load(object sender, EventArgs e)   {     // Instantiate Web service proxy for retrieving data     //     using (PortalServices ps = new PortalServices())     {       // News headlines Web service call       _newsHeadlines.DataSource = ps.GetNewsHeadlines();       _newsHeadlines.DataBind();       // Stock quote Web service call       string[] stocks = new string[] { "MSFT", "IBM", "SUNW",                                             "GOOG", "ORCL" };       _stocksGrid.DataSource = ps.GetStockQuotes(stocks);       _stocksGrid.DataBind();       // Weather report Web service call       _weatherLabel.Text = ps.GetWeatherReport("04090");     }     // Data access     //     string dsn = ConfigurationManager.ConnectionStrings["salesDsn"].                                       ConnectionString;     string sql = "WAITFOR DELAY '00:00:03' SELECT [id], [quarter]," +        "[year], [amount], [projected] FROM [sales] WHERE year=@year";     using (SqlConnection conn = new SqlConnection(dsn))     using (SqlCommand cmd = new SqlCommand(sql, conn))     {       cmd.Parameters.AddWithValue("@year",                 int.Parse(_yearTextBox.Text));       conn.Open();       _salesGrid.DataSource = cmd.ExecuteReader();       _salesGrid.DataBind();     }   } } 

The total time required to retrieve the data and render this page will be the sum of the times taken to invoke the Web services and issue the query to the database (t1-4), plus the time taken to do the normal page processing and data binding (tp1-2), as shown in Figure 9-2. With our explicit delays of three seconds built into every call, this page will take more than 12 seconds to render. This is frustrating not just because it is a long time to wait for the page to display, but because each data retrieval is completely independent of the others and could feasibly be done in parallel, cutting the response time to potentially a quarter of what it is sequentially.

Figure 9-2. Time taken to process a page with sequential data retrieval


Our goal for this type of pageone making several independent requests for datais to parallelize all of the data retrieval calls. If each request is made asynchronously with respect to the primary request thread, the overall time to process requests for this page will no longer be the sum of the times, but instead the maximum of the four data retrieval times (t1-4), plus the time taken to do the normal page processing and data binding (tp1-2), as shown in Figure 9-3. In our sample application with built-in three second delays, we should see a response time closer to three secondsa near four-fold improvement! In many pages, the speed improvements to be gained by introducing parallelism can be quite dramatic, and can often make the difference between an application with unacceptable response times and one that is quite responsive.

Figure 9-3. Time taken to process a page with parallel data retrieval


As you will see, .NET 2.0 has significant support for issuing remote Web service calls and data access calls asynchronously, and the Page class in ASP.NET 2.0 has been augmented to support the management of asynchronous tasks so that parallelizing pages as in our example is completely feasible.

Relaxing Thread-Pool Contention

The other reason to introduce asynchrony into a Web page in ASP.NET 2.0 is to make more efficient use of the thread pool threads. This reason is more esoteric than the straightforward rationale of introducing parallelism. Basically, if you have pages that perform I/O-bound tasks, it may be possible to perform those tasks asynchronously, and relinquish the primary request thread back to the thread pool so that it can be used to service other requests while your page awaits the completion of its tasks.

The purpose of the thread pool is to efficiently allocate CPU time for processing requests with an upper limit on the total number of threads allocated. This is an important restriction to have in place, as creating an unbounded number of threads can be extremely inefficient, and can grind a system to a halt when a large number of requests are made concurrently. While the thread pool is an important element to have in place to make sure the CPU is efficiently shared, it also comes with a cost of its own. The thread pool allocates new threads on demand based on a set of heuristics driven primarily by how many requests for work have been queued. It also monitors CPU utilization, and when things slow down, it takes threads out of the pool too. All of this takes time and resources to manage, so if you can do anything to relax the contention for threads in the thread pool, you will improve the overall efficiency of your application.

If you have pages in your site that spend most of their time waiting for I/O requests to complete (network calls to databases or Web services being the most common), these are ideal pages to consider making asynchronous. Our sample application with three Web service calls and one remote database call turns out to be an ideal candidate for relieving thread-pool contention too. By relinquishing the primary request thread after issuing all of the I/O-bound requests, and then completing the request once all of the I/O-bound requests have completed, it frees up the request thread to service other requests. Fortunately, ASP.NET 2.0 has added support for doing exactly this using a feature called asynchronous pages.




Essential ASP. NET 2.0
Essential ASP.NET 2.0
ISBN: 0321237706
EAN: 2147483647
Year: 2006
Pages: 104

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