Calling Web Service Functions Asynchronously from a Web Page

Over on the Asp.Net forums where I moderate, a user had a problem calling a Web Service from a web page asynchronously. I tried his code on my machine and was able to reproduce the problem. I was able to solve his problem, but only after taking the long scenic route through some of the more perplexing nuances of Web Services and Proxies.

Here is the fascinating story of that journey.

Start with a simple Web Service

    public class Service1 : System.Web.Services.WebService
    {
        [WebMethod]
        public string HelloWorld()
        {
            // sleep 10 seconds
            System.Threading.Thread.Sleep(10 * 1000);
            return "Hello World";
        }
    }

The 10 second delay is added to make calling an asynchronous function more apparent. If you don't call the function asynchronously, it takes about 10 seconds for the page to be rendered back to the client. If the call is made from a Windows Forms application, the application freezes for about 10 seconds.

Add the web service to a web site. Right-click the project and select "Add Web Reference…"

Next, create a web page to call the Web Service.

Note: An asp.net web page that calls an 'Async' method must have the Async property set to true in the page's header:

<%@ Page Language="C#" 
         AutoEventWireup="true" 
         CodeFile="Default.aspx.cs" 
         Inherits="_Default"  
         Async='true'  %>

Here is the code to create the Web Service proxy and connect the event handler. Shrewdly, we make the proxy object a member of the Page class so it remains instantiated between the various events.

public partial class _Default : System.Web.UI.Page 
{
    localhost.Service1 MyService;  // web service proxy
 
    // ---- Page_Load ---------------------------------
 
    protected void Page_Load(object sender, EventArgs e)
    {
        MyService = new localhost.Service1();
        MyService.HelloWorldCompleted += EventHandler;      
    }

Here is the code to invoke the web service and handle the event:

    // ---- Async and EventHandler (delayed render) --------------------------
 
    protected void ButtonHelloWorldAsync_Click(object sender, EventArgs e)
    {
        // blocks
        ODS("Pre HelloWorldAsync...");
        MyService.HelloWorldAsync();
        ODS("Post HelloWorldAsync");
    }
    public void EventHandler(object sender, localhost.HelloWorldCompletedEventArgs e)
    {
        ODS("EventHandler");
        ODS("    " + e.Result);
    }
 
    // ---- ODS ------------------------------------------------
    //
    // Helper function: Output Debug String
 
    public static void ODS(string Msg)
    {
        String Out = String.Format("{0}  {1}", DateTime.Now.ToString("hh:mm:ss.ff"), Msg);
        System.Diagnostics.Debug.WriteLine(Out);
    }

I added a utility function I use a lot: ODS (Output Debug String). Rather than include the library it is part of, I included it in the source file to keep this example simple.

Fire up the project, open up a debug output window, press the button and we get this in the debug output window:

11:29:37.94 Pre HelloWorldAsync...
11:29:37.94 Post HelloWorldAsync
11:29:48.94 EventHandler
11:29:48.94 Hello World  

Sweet. The asynchronous call was made and returned immediately. About 10 seconds later, the event handler fires and we get the result. Perfect….right?

Not so fast cowboy. Watch the browser during the call:

What the heck? The page is waiting for 10 seconds. Even though the asynchronous call returned immediately, Asp.Net is waiting for the event to fire before it renders the page. This is NOT what we wanted.

I experimented with several techniques to work around this issue. Some may erroneously describe my behavior as 'hacking' but, since no ingesting of Twinkies was involved, I do not believe hacking is the appropriate term.

If you examine the proxy that was automatically created, you will find a synchronous call to HelloWorld along with an additional set of methods to make asynchronous calls. I tried the other asynchronous method supplied in the proxy:

    // ---- Begin and CallBack ----------------------------------
 
    protected void ButtonBeginHelloWorld_Click(object sender, EventArgs e)
    {
        ODS("Pre BeginHelloWorld...");
        MyService.BeginHelloWorld(AsyncCallback, null);
        ODS("Post BeginHelloWorld");
    }
    public void AsyncCallback(IAsyncResult ar)
    {
        String Result = MyService.EndHelloWorld(ar);
 
        ODS("AsyncCallback");
        ODS("    " + Result);
    }

The BeginHelloWorld function in the proxy requires a callback function as a parameter. I tested it and the debug output window looked like this:

04:40:58.57 Pre BeginHelloWorld...
04:40:58.57 Post BeginHelloWorld
04:41:08.58 AsyncCallback
04:41:08.58 Hello World

It works the same as before except for one critical difference: The page rendered immediately after the function call. I was worried the page object would be disposed after rendering the page but the system was smart enough to keep the page object in memory to handle the callback.

Both techniques have a use:

Delayed Render: Say you want to verify a credit card, look up shipping costs and confirm if an item is in stock. You could have three web service calls running in parallel and not render the page until all were finished. Nice. You can send information back to the client as part of the rendered page when all the services are finished.

Immediate Render: Say you just want to start a service running and return to the client. You can do that too. However, the page gets sent to the client before the service has finished running so you will not be able to update parts of the page when the service finishes running.

Summary:

YourFunctionAsync() and an EventHandler will not render the page until the handler fires.

BeginYourFunction() and a CallBack function will render the page as soon as possible.

I found all this to be quite interesting and did a lot of searching and researching for documentation on this subject….but there isn't a lot out there. The biggest clues are the parameters that can be sent to the WSDL.exe program:

http://msdn.microsoft.com/en-us/library/7h3ystb6(VS.100).aspx

Two parameters are oldAsync and newAsync. OldAsync will create the Begin/End functions; newAsync will create the Async/Event functions. Caveat: I haven't tried this but it was stated in this article. I'll leave confirming this as an exercise for the student J.

Included Code:

I'm including the complete test project I created to verify the findings. The project was created with VS 2008 SP1. There is a solution file with 3 projects, the 3 projects are:

  • Web Service
  • Asp.Net Application
  • Windows Forms Application

To decide which program runs, you right-click a project and select "Set as Startup Project".

I created and played with the Windows Forms application to see if it would reveal any secrets. I found that in the Windows Forms application, the generated proxy did NOT include the Begin/Callback functions. Those functions are only generated for Asp.Net pages. Probably for the reasons discussed earlier. Maybe those Microsoft boys and girls know what they are doing.

I hope someone finds this useful.

Steve Wellens

9 Comments

  • Hi Steve, thanks for your great article. It helps clarify my understanding well. However, I still haven't overcomed the requirement of my project to call multiple web serivces asynchronously and display the result in various portlets of a page without waiting for all services calls completed. For the service that returns result first should just displays first and the second return should just updating the page...till all calls finish.

    Could you help sharing how to get though this requirement?

    Thanks, Vuthy

  • "Could you help sharing how to get though this requirement?"

    Please post questions on the asp.net forums.

    Thanks.

  • Thanks Steve, Its a very nice article. Good Job

  • I always tend to get confuse when calling Async services. This post is really helpful.

    Thanks for sharing Steve

  • One question that has been bothering me since ages now is "Why whould the page not render if I call the webmethod asynchronously using Event based asynchrnous pattern?"

    This is the only web page in the entire Internet that agrees and explains this point.

    Thanks so much for this blog. Now I understand that I am not the oddball. (but MS surely is)

  • Hi Steve,

    hats off man.............

  • Steve, you helped solve the very puzzle I have not been able to solve for weeks... Thank you very much.

  • "Does this eat up a thread in the web applications thread pool during the long processing? "

    I played around with Process Explorer. &nbsp;Instead of making one async call, I modified my test application to do 10 calls.

    I saw 10 new threads created and then removed.

    I was also surprised to see 10 new TCP connections established and then closed. &nbsp;I don't know what that means...but now you have the information.
    PS:&nbsp; In the future, you probably want to post questions on the forums.

    &nbsp;

  • Excellent post. I might implement this. Our website invokes webservices and the response from the web services is very slow. Few services can be called behind the scenes while the user is entering data in a Wizard screen which has 6 steps - A typical checkout process: Login->Delivery Address->Order Summary->Payment Screen.
    Hope this will work.

Comments have been disabled for this content.