ASP.NET

Asynchronous Execution

The big advantage to using Web services is that they expose functionality literally worldwide. Because the Internet is so far reaching, you can call method between a client located in the United States and a service located in some place like Australia.

One of the downsides involved in making calls over such long distances is the latency. In addition to the expense of context switching, the speed of light is finite. Bits having to travel far distances make for long waits during Web method calls. For that reason, the proxies generated by Visual Studio include an asynchronous calling mechanism replete with completion callbacks.

If you look at the proxy generated by Visual Studio (Visual Studio includes it in the source code set. You may get to it using the Class View, or you may look for the file in the Web References\localhost subdirectory of the project), you'll see multiple versions of the methods exposed by the Web service. For example, there's a GetAQuote method and a Get[[<img src="images/shy.gif"/>]]AQuoteAsync method. The proxy generated by Visual Studio also includes a number of delegates defined for subscribing to completion events.

For example, the callback delegate defined for being notified when the GetAQuoteAsync method is finished looks like this:

    void GetAQuoteCompletedEventHandler(object sender,
           GetAQuoteCompletedEventArgs e);

The event callbacks are set up so that the second argument includes the results of calling the method (in this case, a Quote structure).

To make an asynchronous method call (and then be notified when it's done), you simply need to invent a callback method that matches the corresponding delegate and attach the callback to the instance of the Web service proxy you're working with.

Listing 19-1 augments the ConsumeWebService application to call the GetAQuoteAsync method.

Listing 19-1
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;

namespace ConsumeWebService
{
   class Program
   {

      static void Main(string[] args)
      {
         localhost.Quote quote;
         localhost.QuoteService quoteService;

         quoteService = new localhost.QuoteService();

         for(int i = 0; i < 10; i++)
         {
            quote = quoteService.GetAQuote();
            System.Console.WriteLine("Quote: " + quote._strQuote);
            System.Console.WriteLine( "Originator: " +
                              quote._strOriginatorFirstName + " " +
                              quote._strOriginatorLastName);
            System.Console.WriteLine();
         }

         System.Console.WriteLine();
         localhost.Quote quoteToAdd =
            new localhost.Quote();
         quoteToAdd._strQuote = "256K RAM should be enough for ANYONE";
         quoteToAdd._strOriginatorLastName = "Gates";
         quoteToAdd._strOriginatorFirstName = "Bill";

         quoteService.AddQuote(quoteToAdd);

         DataSet dataSetQuotes = quoteService.GetAllQuotes();

         DataTable tableQuotes = dataSetQuotes.Tables[0];
         foreach (DataRow dr in tableQuotes.Rows)
         {
            System.Console.WriteLine(dr[0] + " " +
            dr[1] + "  "  + dr[2]);
         }
       System.Console.WriteLine(
          "Press enter to fetch a quote using async");
       System.Console.ReadLine();
       quoteService.GetAQuoteCompleted += OnGetAQuoteCompleted;
       quoteService.GetAQuoteAsync();
       System.Console.WriteLine("Press return to end program ");
       System.Console.ReadLine();


  }


      // from generated code  :
      //  public delegate
      //  void GetAQuoteCompletedEventHandler(object sender,
      //     GetAQuoteCompletedEventArgs e);
      public static void OnGetAQuoteCompleted(object sender,
           localhost.GetAQuoteCompletedEventArgs e)
      {
         System.Console.WriteLine();
         System.Console.WriteLine("This is the callback for GetAQuote");
         System.Console.WriteLine("Quote: " + e.Result._strQuote);
         System.Console.WriteLine(e.Result._strOriginatorFirstName +
            " " + e.Result._strOriginatorLastName);
      }

    }
 }

After running the asynchronous version, you should see output like this. The callback should display two randomly selected quotes-the result of calling the GetQuote method twice:

Graphic Graphic

The screen shots look a bit odd here because of the order in which the code runs. The program fetches ten quotes synchronously. Then, waits for the enter key to be pressed (that's the "Press return to end program" line you see. Remember that the last Web method call is running asynchronously, so we see the result of the asynchronous call even as the main thread is waiting for the Enter keypress.

The callback mechanism is especially useful for application environments that cannot afford to stall. For example, if a Windows application takes too long to handle a message (the cardinal rule is 1/10 of a second or so), the entire application begins to suffer. Windows applications require a continuously running message pump. Calling a Web method within a Windows application is likely to stall the application for longer than you want. By calling the async versions of the Web methods, the calling thread returns immediately so it can continue doing whatever it's supposed to be doing (for example, running a message loop).