Section 16.4. Using Asynchronous Method Calls

16.4. Using Asynchronous Method Calls

As mentioned previously in this chapter, in the section on "Proxy Class Details," web services allow the developer to call any of the exposed web methods either synchronously or asynchronously.

Figure 16-6. StockTickerConsumer in action

When a method is called synchronously, which is the "normal" way of doing method calls, the program execution waits for the method to return. As long as the method does not take too long to process and there is not too much network delay, this pause will not be a problem.

Figure 16-7 shows synchronous processing . Methods are called on the server via the proxy. The calling program is unaware a proxy is intervening in the process. A call goes out and when the results come back, the calling program continues processing.

Figure 16-7. Synchronous method calls

However, in situations where the method is time-consuming to process (for example, a lengthy database operation or extensive computation) or where the network delay is significant, then this delay can be an unacceptable performance hit. In the case of web services, where all the method calls entail a round trip over the Internet, long network delays are common. Broadband Internet connections can help, but performance will still suffer.

One solution is to use asynchronous processing. In this model, a web service method is called, with instructions to notify the client when the result is ready. The client can go about its business, not waiting for the method to return. When the asynchronous method completes, a callback method is called. The client then retrieves the data from the server. Asynchronous processing is shown schematically in Figure 16-8.

Figure 16-8. Asynchronous method calls

As with the synchronous method call, the client is unaware the proxy is intercepting the method call and passing it along to the server. The client event handler calls the Begin... method on the web service (actually on the proxy) passing in a delegate for the callback method (step 1 in Figure 16-8). The client then goes on to do other work.

The proxy calls the web method on behalf of the client (step 2). When the server has completed the method, it returns the result to the proxy (step 3). The proxy calls the client's callback method and passes in an object implementing IAsyncResult (step 4).

The client passes that IAsyncResult back to the proxy's End... method (step 5). The End... method then returns the data to the client (step 6).

The client does not have to poll the server; it is notified by the callback when the method completes.

The callback method is a delegate , which is a reference type that encapsulates a method with a specific signature and return type. The async Begin... and End... methods define a delegate for the callback mechanism you implement in your client.

To illustrate the use of delegates and asynchronous proxy calls, create a new web site named StockTickerConsumerAsync in VS2005. The page layout should look like Figure 16-9. It is nearly identical to the layout used in the StockTickerConsumer example, shown in Figure 16-4 and listed in Example 16-4, including the panel used to control the visibility of the labels it contains.

Figure 16-9. Asynchronous web page layout

The only difference between this example and the previous one is that there are now no field or buttons for Stock Exchange, the Get History button from Figure 16-4 is labeled Get Data, and the button ID is now btnGetData . In addition, the AutoPostBack property of the txtFirmNameStockSymbol and txtPriceStockSymbol text boxes should be set to false .

This web page will accept stock symbols in each of the text fields. When the Get Data button is clicked, all the processing for each field will be done asynchronously. If this were a real-world application where each field would typically be hitting a different web service on different servers, this asynchronous processing would prevent one slow connection from holding up the works. All three web service calls will essentially be occurring simultaneously .

Before entering the asynchronous code, you will make the page work synchronously to see how it works. To the end user , the synchronous and asynchronous implementations will look identical, except the latter should be somewhat faster (though that will not be noticeable in this example, where all of the web method calls are going to localhost).

Add the single event handler to the Get Data button by double-clicking on the button in Design view. This will bring you to the btnGetData_Click event handler in the code-behind page. Enter the code in Example 16-9 to the event handler.

Example 16-9. Synchronous event handler for btnGetData
 lblFirmName.Text = proxy.GetName(txtFirmNameStockSymbol.Text); lblStockPrice.Text = "$ " +     Convert.ToString(proxy.GetPrice(txtPriceStockSymbol.Text)); Stock theStock = proxy.GetHistory(txtHistoryStockSymbol.Text); string StockName = theStock.StockName; double StockPrice = theStock.Price; DateTime TradeDate1 = theStock.History[0].TradeDate; double Price1 = theStock.History[0].Price; DateTime TradeDate2 = theStock.History[1].TradeDate; double Price2 = theStock.History[1].Price; //  Display the results. pnlHistory.Visible = true; lblHistoryStockName.Text = StockName; lblHistoryStockPrice.Text = "$ " + Convert.ToString(StockPrice); lblHistoryDate1.Text =TradeDate1.ToString("d"); lblHistoryPrice1.Text = "$ " + Convert.ToString(Price1); lblHistoryDate2.Text = TradeDate2.ToString("d"); lblHistoryPrice2.Text = "$ " + Convert.ToString(Price2); 

This code is identical to that in Example 16-8, except it is condensed into a single event handler rather than spread over three different event handlers.

Run the web application, fill in the stock symbols, and press the Get Data button; the resulting web page will look something like Figure 16-10.

Before adding the code to convert this web page from synchronous processing to asynchronous processing, examine the proxy class source code shown in Example 16-6. That segment of the source code shows the proxy method calls available for the GetHistory method. There are five (one of which has two overloaded forms), but we are particularly interested in three of them:

Figure 16-10. Synchronous test result



GetHistory

This is the synchronous method. It takes a single parameter, the StockSymbol string.



BeginGetHistory

This method starts the asynchronous processing. It takes three parameters: the StockSymbol string, the delegate callback method of type AsyncCallback , and an object called asyncState .



EndGetHistory

This method takes a single parameter, asyncResult , which is of type IAsyncResult .

Each of the methods exposed in the web service has equivalent Begin... and End... methods to enable asynchronous processing.

The code in Example 16-10 shows the complete code listing for the code-behind page demonstrating asynchronous event handling for a web service consumer. The lines of code relevant to converting the event handling from synchronous to asynchronous are highlighted.

Example 16-10. default.aspx.cs forStockTickerConsumerAsync
 using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls;  using System.Threading;    //  necessary for async operation  public partial class _Default : System.Web.UI.Page {  //  Create delegates.    private AsyncCallback myCallBackFirmNameStockSymbol;    private AsyncCallback myCallBackPriceStockSymbol;    private AsyncCallback myCallBackHistory;    StockTickerSoap proxy = new StockTickerSoap(  );    int flags;  //  default constructor for the class  public _Default(  )    {       // assign the call back       myCallBackFirmNameStockSymbol = new                      AsyncCallback(this.onCompletedGetName);       myCallBackPriceStockSymbol = new                      AsyncCallback(this.onCompletedGetPrice);       myCallBackHistory = new                      AsyncCallback(this.onCompletedGetHistory);    }  protected void Page_Load(object sender, EventArgs e)    {    }    protected void txtFirmNameStockSymbol_TextChanged(object sender,                                                      EventArgs e)    {       lblFirmName.Text = proxy.GetName(txtFirmNameStockSymbol.Text);    }    protected void txtPriceStockSymbol_TextChanged(object sender,                                                   EventArgs e)    {       lblStockPrice.Text = "$ " +          Convert.ToString(proxy.GetPrice(txtPriceStockSymbol.Text));    }  protected void btnGetData_Click(object sender, EventArgs e)    {       flags = 0;       // lblFirmName.Text = proxy.GetName(txtFirmNameStockSymbol.Text);       proxy.BeginGetName(txtFirmNameStockSymbol.Text,                          myCallBackFirmNameStockSymbol,                          0);       // lblStockPrice.Text = "$ " +       Convert.ToString(proxy.GetPrice(txtPriceStockSymbol.Text));       proxy.BeginGetPrice(txtPriceStockSymbol.Text,                           myCallBackPriceStockSymbol,                           0);       // Stock theStock = proxy.GetHistory(txtHistoryStockSymbol.Text);       proxy.BeginGetHistory(txtHistoryStockSymbol.Text,                             myCallBackHistory,                             0);       while (flags < 3)       {          Thread.Sleep(100);       }    }    private void onCompletedGetName(IAsyncResult asyncResult)    {       string s = proxy.EndGetName(asyncResult);       lblFirmName.Text = s;       flags++;    }    private void onCompletedGetPrice(IAsyncResult asyncResult)    {       lblStockPrice.Text = "$ " +                      Convert.ToString(proxy.EndGetPrice(asyncResult));       flags++;    }    private void onCompletedGetHistory(IAsyncResult asyncResult)    {       Stock theStock = proxy.EndGetHistory(asyncResult);       string StockName = theStock.StockName;       double StockPrice = theStock.Price;       DateTime TradeDate1 = theStock.History[0].TradeDate;       double Price1 = theStock.History[0].Price;       DateTime TradeDate2 = theStock.History[1].TradeDate;       double Price2 = theStock.History[1].Price;       //  Display the results.       pnlHistory.Visible = true;       lblHistoryStockName.Text = StockName;       lblHistoryStockPrice.Text = "$ " + Convert.ToString(StockPrice);       lblHistoryDate1.Text =TradeDate1.ToString("d");       lblHistoryPrice1.Text = "$ " + Convert.ToString(Price1);       lblHistoryDate2.Text = TradeDate2.ToString("d");       lblHistoryPrice2.Text = "$ " + Convert.ToString(Price2);       flags++;    }  } 

The first step is to declare the delegates with the following lines of code inside the _Default class:

 private AsyncCallback myCallBackFirmNameStockSymbol;     private AsyncCallback myCallBackPriceStockSymbol;     private AsyncCallback myCallBackHistory; 

These lines declare the delegates as private members of the class. The delegates are of type AsyncCallback . This is the same type as the second parameter required by the Begin... methods.

An AsyncCallback delegate is declared in the System namespace as follows :

 public delegate void AsyncCallback (IAsyncResult ar); 

Thus, this delegate can be associated with any method that returns void and takes the IAsyncResult interface as a parameter.

You will create three methods in your client to act as callback methods : onCompletedGetName , onCompletedGetPrice , and onCompletedGetHistory . You encapsulate these methods within their delegates in the constructor as follows:

 myCallBackFirmNameStockSymbol = new                    AsyncCallback(this.onCompletedGetName);     myCallBackPriceStockSymbol = new AsyncCallback(this.onCompletedGetPrice);     myCallBackHistory = new AsyncCallback(this.onCompletedGetHistory); 

You will see how to implement the three callback methods shortly.

The next step is to call all the Begin... methods to start the asynchronous processing. Replace each of the lines of code in btnGetData_Click that calls one of the proxy methods with its equivalent Begin... method. (For now, comment out the original lines of code and keep them for reference.) The first parameter for each Begin... method is the same as the parameter for the original synchronous method. The second parameter is the delegate created previously. The third parameter is an object for maintaining state, if necessary. For this example, use zero. The btnGetData_Click event procedure should appear as follows:

  private void btnGetData_Click(object sender, System.EventArgs e)     {        flags = 0;  // lblFirmName.Text = proxy.GetName(txtFirmNameStockSymbol.Text);  proxy.BeginGetName(txtFirmNameStockSymbol.Text,                           myCallBackFirmNameStockSymbol,                           0);  // lblStockPrice.Text = "$ " +              Convert.ToString(proxy.GetPrice(txtPriceStockSymbol.Text));  proxy.BeginGetPrice(txtPriceStockSymbol.Text,                            myCallBackPriceStockSymbol,                            0);  // Stock theStock = proxy.GetHistory(txtHistoryStockSymbol.Text);  proxy.BeginGetHistory(txtHistoryStockSymbol.Text,                              myCallBackHistory,                              0);        while (flags < 3)        {           Thread.Sleep(100);        }     }  

The flags variable and the while loop will be explained shortly.

Create the three callback methods, and move the code from the synchronous btnGetData_Click to the appropriate method. Call the new methods onCompletedGet- Name , onCompletedGetPrice , and onCompletedGetHistory . The contents of these methods is shown in Example 16-9.

In each of the callback methods, the End... method associated with the appropriate web method in the proxy is called to construct the label Text properties to be set for display in the web page. In onCompletedGetName , a string is set to the return value from the proxy.EndGetName method. This string is then assigned to the label Text property. onCompletedGetPrice uses a similar technique, using a single line of code to replace the two lines in onCompletedGetName . onCompletedGetHistory is similar, except that it instantiates a Stock object with the return value from proxy . EndGetHistory . As you will recall from Example 16-5, a Stock object contains a stock symbol, stock name, price, and an array of StockHistory objects.

The last thing to explain is the flags variable, which is a counter. This variable is declared as a member variable:

 int flags; 

Each one of the callback methods increments the flags counter:

 flags++; 

Within the button click event handler, btnGetData_Click , the flags counter is reset to zero. Then every callback method increments the counter. The while loop prevents the button click event from completing until all three callback method methods have completed:

 while (flags < 3)     {        Thread.Sleep(100);     } 

When the web page is run, the three Begin... methods are called. As each returns results, the onCompleted... methods call the appropriate End method and increment the counter.

When the counter reaches 3, the web page redraws. The end result looks indistinguishable from that shown in Figure 16-10.

Asynchronous consumption of web services can be very useful under the correct circumstances but may not scale well because each asynchronous method call spawns a new thread. So, StockTickerConsumerAsync would spawn three additional threads in addition to the main thread. This would be fine for a low-volume web site, but the performance penalty could overwhelm a large, busy web site.




Programming ASP. NET
Programming ASP.NET 3.5
ISBN: 0596529562
EAN: 2147483647
Year: 2003
Pages: 173

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