Share via


Server-Side Asynchronous Web Methods

 

Matt Powell
Microsoft Corporation

October 2, 2002

Summary: Matt Powell shows how to make use of asynchronous Web methods on the server side to create high performance Microsoft ASP.NET Web services. (8 printed pages)

Introduction

In my September 3rd column, I wrote about calling Web services asynchronously over HTTP using the client-side capabilities of the Microsoft® .NET Framework. This approach is an extremely useful way to make calls to a Web service without locking up your application or spawning a bunch of background threads. Now we are going to look at asynchronous Web methods that provide similar capabilities on the server side. Asynchronous Web methods are similar to the high performance provided by the HSE_STATUS_PENDING approach to writing ISAPI extensions, but without the coding overhead of having to manage your own thread pool, and with all the benefits of running in managed code.

First let's consider normal, synchronous Microsoft® ASP.NET Web methods. The response for a synchronous Web method is sent when you return from the method. If it takes a relatively long period of time for a request to complete, then the thread that is processing the request will be in use until the method call is done. Unfortunately, most lengthy calls are due to something like a long database query, or perhaps a call to another Web service. For instance, if you make a database call, the current thread waits for the database call to complete. The thread has to simply wait around doing nothing until it hears back from its query. Similar issues arise when a thread waits for a call to a TCP socket or a backend Web service to complete.

Waiting threads are bad—particularly in stressed server scenarios. Waiting threads don't do anything productive, like servicing other requests. What we need is a way to start a lengthy background process on a server, but return the current thread to the ASP.NET process pool. Then, when the lengthy background process completes, we would like to have a callback function invoked so that we can finish processing the request and somehow signal the completion of the request to ASP.NET. As it turns out, this capability is provided by ASP.NET with asynchronous Web methods.

How Asynchronous Web Methods Work

When you write a typical ASP.NET Web service using Web methods, Microsoft® Visual Studio® .Net simply compiles your code to create the assembly that will be called when requests for its Web methods are received. The assembly itself doesn't know anything about SOAP. Therefore when your application is first launched, the ASMX handler must reflect over your assembly to determine which Web methods are exposed. For normal, synchronous requests, it is simply a matter of finding which methods have a WebMethod attribute associated with them, and setting up the logic to call the right method based off of the SOAPAction HTTP header.

For asynchronous requests, during reflection the ASMX handler looks for Web methods with a certain kind of signature that it recognizes as being asynchronous. In particular, it looks for a pair of methods that have the following rules:

  • There is a ,BeginXXX and EndXXX Web method where XXX is any string that represents the name of the method you want to expose.
  • The ,BeginXXX function returns an IAsyncResult interface and takes as its last two input parameters an AsyncCallback, and an object respectively.
  • The EndXXX function takes as its only parameter an IAsyncResult interface.
  • Both must be flagged with the WebMethod attribute.

If the ASMX handler finds two methods that meet all these requirements, then it will expose the method XXX in its WSDL as if it were a normal Web method. The method will accept the parameters defined before the AsyncCallback parameter in the signature for ,BeginXXX as input, and it will return what is returned by the EndXXX function. So if we had a Web method whose synchronous declaration looked like this:

    [WebMethod]
    public string LengthyProcedure(int milliseconds) {...}

Then an asynchronous declaration would look like this:

    [WebMethod]
    public IAsyncResult BeginLengthyProcedure(
                            int milliseconds, 
                            AsyncCallback cb, 
                            object s) {...}

    [WebMethod]
    public string EndLengthyProcedure(IAsyncResult call) {...}

The WSDL for each would be the same.

After the ASMX handler reflects on an assembly and detects an asynchronous Web method, it must handle requests for that method differently than it handles synchronous requests. Instead of calling a simple method, it calls the ,BeginXXX method. It deserializes the incoming request into the parameters to be passed to the function—as it does for synchronous requests—but it also passes the pointer to an internal callback function as the extra AsyncCallback parameter to the ,BeginXXX method.

This approach is purposefully similar to the asynchronous programming paradigm in the .NET Framework for Web service client applications. In the case of the client-side support for asynchronous Web service calls, you free up blocked threads for the client machine, while on the server side we free up blocked threads on the server machine. There are two key differences, however. First of all, instead of your server code calling the ,BeginXXX and EndXXX functions, the ASMX handler will call them instead. Secondly, you will write the code for the BeginXXX and EndXXX functions instead of using code generated by WSDL.EXE or the "Add Web Reference" Wizard in Visual Studio .NET. However, the result—freeing up threads so that they can perform some other processing—is the same.

After the ASMX handler calls your server's ,BeginXXX function, it will return the thread to the process thread pool so it can handle any other requests that are received. The HttpContext for the request will not be released yet. The ASMX handler will wait until the callback function that it passed to the ,BeginXXX function is called for it to finish processing the request.

Once the callback function is called, the ASMX handler will call the EndXXX function so that your Web method can complete any processing it needs to perform, and the return data can be supplied that will be serialized into the SOAP response. Only when the response is sent after the EndXXX function returns will the HttpContext for the request be released.

A Simple Asynchronous Web Method

To illustrate asynchronous Web methods, I start with a simple synchronous Web method called LengthyProcedure, whose code is shown below. We will then look at how to do the same thing asynchronously. LengthyProcedure simply blocks for the given number of milliseconds.

[WebService]
public class SyncWebService : System.Web.Services.WebService
{
    [WebMethod]
    public string LengthyProcedure(int milliseconds) 
    { 
        System.Threading.Thread.Sleep(milliseconds);
        return "Success"; 
    }
}

Now we will convert LengthyProcedure to an asynchronous Web method. We must create a BeginLengthyProcedure function and an EndLengthyProcedure function as we described earlier. Remember that our BeginLengthyProcedure call will need to return an IAsyncResult interface. In this case I am going to have our BeginLengthyProcedure call make an asynchronous method invocation using a delegate and the BeginInvoke method on that delegate. The callback function passed to BeginLengthyProcedure will be handed over to the BeginInvoke method on our delegate, and the IAsyncResult returned from BeginInvoke will be returned by the BeginLengthyProcedure method.

The EndLengthyProcedure method will be called when our delegate is completed. We will call the EndInvoke method on the delegate passing in the IAsyncResult that we received as input to the EndLengthyProcedure call. The returned string will be the string returned from our Web method. Here is the code:

[WebService]
public class AsyncWebService : System.Web.Services.WebService
{
    public delegate string LengthyProcedureAsyncStub(
        int milliseconds);

    public string LengthyProcedure(int milliseconds) 
    { 
        System.Threading.Thread.Sleep(milliseconds);
        return "Success"; 
    }

    public class MyState 
    { 
        public object previousState; 
        public LengthyProcedureAsyncStub asyncStub; 
    }

    [ System.Web.Services.WebMethod ]
    public IAsyncResult BeginLengthyProcedure(int milliseconds, 
        AsyncCallback cb, object s)
    {
        LengthyProcedureAsyncStub stub 
            = new LengthyProcedureAsyncStub(LengthyProcedure);
        MyState ms = new MyState();
        ms.previousState = s; 
        ms.asyncStub = stub;
        return stub.BeginInvoke(milliseconds, cb, ms);
    }
  
    [ System.Web.Services.WebMethod ]
    public string EndLengthyProcedure(IAsyncResult call)
    {
        MyState ms = (MyState)call.AsyncState;
        return ms.asyncStub.EndInvoke(call);
    }
}

When Do Asynchronous Web Methods Make Sense

There are several issues to consider when determining whether asynchronous Web methods make sense for your application. First of all, the ,BeginXXX function for your call must return an IAsyncResult interface. IAsyncResults are returned from a number of asynchronous I/O operations for accessing streams, making Microsoft® Windows® Sockets calls, performing file I/O, interacting with other hardware devices, calling asynchronous methods, and of course calling other Web services. You will most likely want to get the IAsyncResult from one of these types of asynchronous operations, so that you can return it from your ,BeginXXX function. The other option is to create your own class that implements the IAsyncResult interface, but then you would more than likely be wrapping one of the previously mentioned I/O implementations anyway.

For almost all of the asynchronous operations we mentioned, using asynchronous Web methods to wrap the backend asynchronous call makes a lot of sense and will result in more efficient Web service code. The exception is when you make asynchronous method calls using delegates. Delegates will cause the asynchronous method calls to execute on a thread in the process thread pool. Unfortunately, these are the same threads used by the ASMX handler to service incoming requests. So unlike calls that are performing real I/O operations against hardware or networking resources, an asynchronous method call using delegates will still block one of the process threads during execution. You might as well block the original thread and have your Web method run synchronously.

The following example shows an asynchronous Web method that calls a backend Web service. It has flagged the BeginGetAge and EndGetAge methods with the WebMethod attribute so that it will run asynchronously. The code for this asynchronous Web method calls a backend Web method called UserInfoQuery to get the information it needs to return. The call to UserInfoQuery is performed asynchronously and is passed the AsyncCallback function that was passed to the BeginGetAge method. This will cause the internal callback function to be called when the backend request completes. The callback function will then call our EndGetAge method to complete the request. In this case the code is much simpler than our previous example, and has the added benefit that it is not launching the backend processing in the same thread pool that is servicing our middle tier Web method requests.

[WebService]
public class GetMyInfo : System.Web.Services.WebService
{
    [WebMethod]
    public IAsyncResult BeginGetAge(AsyncCallback cb, Object state)
    {
        // Invoke an asynchronous Web service call.
        localhost.UserInfoQuery proxy 
            = new localhost.UserInfoQuery();
        return proxy.BeginGetUserInfo("User's Name", 
                                      cb, 
                                      proxy);
    }

    [WebMethod]
    public int EndGetAge(IAsyncResult res)
    {
        localhost.UserInfoQuery proxy 
            = (localhost.UserInfoQuery)res.AsyncState;
        int age = proxy.EndGetUserInfo(res).age;
        //  Do any additional processing on the results 
        //  from the Web service call here.
        return age;
    }
}

One of the most common types of I/O operations that occur within a Web method is a call to a SQL database. Unfortunately, Microsoft® ADO.NET does not have a good asynchronous calling mechanism defined at this time, and simply wrapping a SQL call in an asynchronous delegate call does not help in the efficiency department. Caching results is sometimes an option, but you should also consider using the Microsoft SQL Server 2000 Web Services Toolkit to expose your databases as a Web service. You will then be able to use the support in the .NET Framework for calling Web services asynchronously to query or update your database.

The lesson with accessing SQL through a Web service call is one that should be taken to heart for many of your backend resources. If you have been using TCP sockets to communicate with a Unix machine, or are accessing some of the other SQL platforms available through proprietary database drivers—or even if you have a resource you have been accessing using DCOM—you might consider using the numerous Web service toolkits that are available today to expose the resources as Web services.

One of the benefits of taking this approach is that you can take advantage of the advances in the client-side Web service infrastructure, such as asynchronous Web service calls with the .NET Framework. Thus you will get asynchronous calling capabilities for free, and your client-access mechanism will just happen to work efficiently with asynchronous Web methods.

Aggregating Data with an Asynchronous Web Method

Many Web services today access multiple resources on the backend and aggregate the information for the front-end Web service. Even though calling multiple backend resources adds complexity to the asynchronous Web method model, there are plenty of efficiencies gained.

Say your Web method is calling two backend Web services, Service A and Service B. From your ,BeginXXX function, you can call Service A asynchronously and Service B asynchronously. You should pass to each of these asynchronous calls your own callback function. In order to trigger completion of the Web method after you receive the results from both Service A and Service B, the callback function you supplied will verify both requests are complete, do any processing on the returned data, and then will call the callback function passed to your ,BeginXXX function. This will trigger the call to your EndXXX function, which upon its return will causes the asynchronous Web method to complete.

Conclusion

Asynchronous Web methods provide an efficient mechanism within ASP.NET Web services for invoking calls to backend services without causing precious threads in the process thread pool to block while doing nothing. In combination with asynchronous requests to backend resources, a server can maximize the number of simultaneous requests they can handle with their Web methods. You should consider this approach for developing high-performance Web service applications.

 

At Your Service

Matt Powell is a member of the MSDN Architectural Samples team, where he helped develop the groundbreaking SOAP Toolkit 1.0. Matt's other achievements include co-authoring Running Microsoft Internet Information Server from Microsoft Press, writing numerous magazine articles, and having a beautiful family to come home to every day.