Communicating with XML Web Services Asynchronously

Communicating with an XML Web service asynchronously follows the asynchronous design pattern used by the rest of the Microsoft .NET Framework. Before you get to those details, however, it is important to note that an XML Web service does not have to be specifically written to handle asynchronous requests to be called asynchronously. The proxy class you create for your client with the Web Services Description Language Tool (Wsdl.exe) automatically creates methods for calling the XML Web service method asynchronously. This is true even if there is only a synchronous implementation of the XML Web service method.

.NET Framework Asynchronous Method Invocation Design Pattern

The design pattern for calling methods asynchronously, specified by the .NET Framework, predicates that there are two asynchronous methods for each synchronous method. For each synchronous method, there is a Begin asynchronous method and an End asynchronous method. The Begin method is called by a client to start or begin the method call. That is, the client instructs the method to start processing the method call, but return immediately. The End method is called by the client to get the results of the processing performed by the XML Web service method call.

How does a client know when to call the End ** method? There are two methods for implementing a client to determine this, as defined by the .NET Framework. The first is to pass a callback function into the Begin method, which is then called when the method has completed processing. The second uses one of the methods of the WaitHandle class to cause a client to wait for the method to complete. When a client implements the second method and calls the Begin ** method, the return value is not the data type specified by the XML Web service method, but rather a type implementing the IAsyncResult interface. The IAsyncResult interface contains an AsyncWaitHandle property of type WaitHandle, which implements methods that support waiting for synchronization objects to become signaled with WaitHandle.WaitOne, WaitAny, and WaitAll. When a synchronization object is signaled, it is an indication that threads waiting upon the specified resource can then access the resource. If an XML Web service client uses the wait method to asynchronously call only one XML Web service method, it can call WaitOne to wait for the processing of that XML Web service method to complete.

It is important to note that regardless of which of the two methods a client chooses to communicate with an XML Web service asynchronously, the SOAP messages sent and received are identical to when communicating synchronously. That is, there is still only one SOAP request and SOAP response sent and received across the network. The proxy class accomplishes this by handling the SOAP response using a different thread than the thread the client used to call the Begin method. Therefore, the client can continue to perform other work on its thread, while the proxy class handles receiving and processing the SOAP response.

Implementing an XML Web Service Client That Makes an Asynchronous Method Call

The infrastructure for making an asynchronous call from an XML Web service client created using ASP.NET to an XML Web service is built into the .NET Framework and the proxy class built by the Web Services Description Language Tool (Wsdl.exe). The design pattern for calling asynchronously is defined by the .NET Framework, and the proxy class provides the mechanism for communicating with an XML Web service method asynchronously. When a proxy class is built for an XML Web service using Wsdl.exe, there are three methods created for each public XML Web service method in the XML Web service. The following table describes those three methods.

Method name in proxy class Description
<NameOfWebServiceMethod> Sends a message for the XML Web service method named <NameOfWebServiceMethod> synchronously.
Begin<NameOfWebServiceMethod> Begins asynchronous message communication with an XML Web service method named <NameOfWebServiceMethod>.
End<NameOfWebServiceMethod> Ends an asynchronous message communication with an XML Web service method named <NameOfWebServiceMethod>, getting the completed message from the XML Web service method.

The following code example is an XML Web service method that might take a relatively extended period of time to finish processing. Thus, it is a good example of when you should set your XML Web service client to call the XML Web service method asynchronously.

<%@ WebService Language="C#" Class="PrimeFactorizer" %>

using System;
using System.Collections;
using System.Web.Services;

class PrimeFactorizer {

[WebMethod]
public long[] Factorize(long factorizableNum){
      ArrayList outList = new ArrayList();
      long i = 0;
      int j;
      try{
            long Check = factorizableNum;
      
            //Go through every possible integer
            //factor between 2 and factorizableNum / 2.
            //Thus, for 21, check between 2 and 10.
            for (i = 2; i < (factorizableNum / 2); i++){
                  while(Check % i == 0){
                        outList.Add(i);
                        Check = (Check/i);
                  }
            }
            //Double-check to see how many prime factors have been added.
            //If none, add 1 and the number.
            j = outList.Count;
            if (j == 0) {
                  outList.Add(1);
                  outList.Add(factorizableNum);
            }
            j = outList.Count;
            
            //Return the results and
            //create an array to hold them.
            long[] primeFactor = new long[j];
            for (j = 0; j < outList.Count; j++){
                  //Pass the values one by one, making sure
                  //to convert them to type ulong.
                  primeFactor[j] = Convert.ToInt64(outList[j]);
            }
            return primeFactor;
      }
      catch (Exception) {
            return null;
      }
}
} 
[Visual Basic]<%@ WebService Class="PrimeFactorizer" Language="VB" %>
Imports System
Imports System.Collections
Imports System.Web.Services

Public Class PrimeFactorizer   
    <WebMethod> _
    Public Function Factorize(factorizableNum As Long) As Long()
        Dim outList As New ArrayList()
        Dim i As Long = 0
        Dim j As Integer
        Try
            Dim Check As Long = factorizableNum
            
            'Go through every possible integer
            'factor between 2 and factorizableNum / 2.
            'Thus, for 21, check between 2 and 10.
            For i = 2 To CLng(factorizableNum / 2) - 1
                While Check Mod i = 0
                    outList.Add(i)
                    Check = CLng(Check / i)
                End While
            Next i
            'Double-check to see how many prime factors have been added.
            'If none, add 1 and the number.
            j = outList.Count
            If j = 0 Then
                outList.Add(1)
                outList.Add(factorizableNum)
            End If
            j = outList.Count
            
            'Return the results and
            'create an array to hold them.
            Dim primeFactor(j - 1) As Long
            For j = 0 To outList.Count - 1
                'Pass the values one by one, making sure
                'to convert them to type ulong.
                primeFactor(j) = CLng(outList(j))
            Next j
            Return primeFactor
        Catch
            Return Nothing
        End Try
    End Function
End Class

The following code example is a portion of a proxy class generated by Wsdl.exe for the above XML Web service method. Note the BeginFactorize and EndFactorize methods, as these are used to communicate with the Factorize XML Web service method asynchronously.

public class PrimeFactorizer : System.Web.Services.Protocols.SoapHttpClientProtocol {
        
        public long[] Factorize(long factorizableNum) {
            object[] results = this.Invoke("Factorize", new object[] {
                        factorizableNum});
            return ((long[])(results[0]));
        }
        
        public System.IAsyncResult BeginFactorize(long factorizableNum, System.AsyncCallback callback, object asyncState) {
            return this.BeginInvoke("Factorize", new object[] {
                        factorizableNum}, callback, asyncState);
        }
        
        public long[] EndFactorize(System.IAsyncResult asyncResult) {
            object[] results = this.EndInvoke(asyncResult);
            return ((long[])(results[0]));
        }
    }

There are two methods for communicating with an XML Web service method asynchronously. The following code example demonstrates communicating with an XML Web service method asynchronously and using a callback function to retrieve the results from the XML Web service method.

using System;
using System.Runtime.Remoting.Messaging;
using MyFactorize;

class TestCallback
 {           
      public static void Main(){
            long factorizableNum = 12345;
            PrimeFactorizer pf = new PrimeFactorizer();

            //Instantiate an AsyncCallback delegate to use as a parameter
            //in the BeginFactorize method.
            AsyncCallback cb = new AsyncCallback(TestCallback.FactorizeCallback);

          // Begin the Async call to Factorize, passing in our
          // AsyncCalback delegate and a reference
          // to our instance of PrimeFactorizer.
            IAsyncResult ar = pf.BeginFactorize(factorizableNum, cb, pf);
            
            // Keep track of the time it takes to complete the async call
            // as the call proceeds.
         int start = DateTime.Now.Second;
         int currentSecond = start;
         while (ar.IsCompleted == false){
            if (currentSecond < DateTime.Now.Second) {
                  currentSecond = DateTime.Now.Second;
                  Console.WriteLine("Seconds Elapsed..." + (currentSecond - start).ToString() );
            }
         }
         // Once the call has completed, you need a method to ensure the
         // thread executing this Main function 
         // doesn't complete prior to the call-back function completing.
         Console.Write("Press Enter to quit");
         int quitchar = Console.Read();
      }
      // Set up a call-back function that is invoked by the proxy class
      // when the asynchronous operation completes.
      public static void FactorizeCallback(IAsyncResult ar)
      {
          // You passed in our instance of PrimeFactorizer in the third
          // parameter to BeginFactorize, which is accessible in the
          // AsyncState property.
          PrimeFactorizer pf = (PrimeFactorizer) ar.AsyncState;
          long[] results;

          // Get the completed results.
            results = pf.EndFactorize(ar);
          
          //Output the results.
            Console.Write("12345 factors into: ");
            int j;
            for (j = 0; j<results.Length;j++){
                  if (j == results.Length - 1)
                      Console.WriteLine(results[j]);
                  else 
                      Console.Write(results[j] + ", ");
            }
      }
}
[Visual Basic]Imports System
Imports System.Runtime.Remoting.Messaging
Imports MyFactorize

Public Class TestCallback
      Public Shared Sub Main()
            Dim factorizableNum As Long = 12345
            Dim pf As PrimeFactorizer = new PrimeFactorizer()

            'Instantiate an AsyncCallback delegate to use as a parameter
            ' in the BeginFactorize method.
            Dim cb as AsyncCallback 
          cb = new AsyncCallback(AddressOf TestCallback.FactorizeCallback)

          ' Begin the Async call to Factorize, passing in the
          ' AsyncCallback delegate and a reference to our instance
          ' of PrimeFactorizer.
          Dim ar As IAsyncResult = pf.BeginFactorize(factorizableNum, _
                                                     cb, pf)
            
          ' Keep track of the time it takes to complete the async call as
          ' the call proceeds.
         Dim start As Integer = DateTime.Now.Second
         Dim currentSecond As Integer = start
         Do while (ar.IsCompleted = false)
            If (currentSecond < DateTime.Now.Second) Then
                  currentSecond = DateTime.Now.Second
                  Console.WriteLine("Seconds Elapsed..." + (currentSecond - start).ToString() )
            End If
         Loop

         ' Once the call has completed, you need a method to ensure the
         ' thread executing this Main function 
         ' doesn't complete prior to the callback function completing.
         Console.Write("Press Enter to quit")
         Dim quitchar As Integer = Console.Read()
      End Sub

      ' Set up the call-back function that is invoked by the proxy 
      ' class when the asynchronous operation completes.
      Public Shared Sub FactorizeCallback(ar As IAsyncResult)
      
          ' You passed in the instance of PrimeFactorizer in the third
          ' parameter to BeginFactorize, which is accessible in the
          ' AsyncState property.

          Dim pf As PrimeFactorizer = ar.AsyncState
          Dim results() as Long

          ' Get the completed results.
            results = pf.EndFactorize(ar)
          
          'Output the results.
            Console.Write("12345 factors into: ")
            Dim j as Integer
            For j = 0 To results.Length - 1
                  If  j = (results.Length - 1) Then
                      Console.WriteLine(results(j) )
                  Else 
                      Console.Write(results(j).ToString + ", ")
                    End If
          Next j         
      End Sub      
End Class

The following code example demonstrates communicating with an XML Web service method asynchronously and then using a synchronization object to wait for the processing to finish.

// -----------------------------------------------------------------------// Async Variation 2.
// Asynchronously invoke the Factorize method, 
//without specifying a call back.
using System;
using System.Runtime.Remoting.Messaging;
// MyFactorize, is the name of the namespace in which the proxy class is
// a member of for this sample.
using MyFactorize;  

class TestCallback
 {          
      public static void Main(){
            long factorizableNum = 12345;
            PrimeFactorizer pf = new PrimeFactorizer();

          // Begin the Async call to Factorize.
            IAsyncResult ar = pf.BeginFactorize(factorizableNum, null, null);

          // Wait for the asynchronous operation to complete.
          ar.AsyncWaitHandle.WaitOne();

          // Get the completed results.
          long[] results;     
          results = pf.EndFactorize(ar);
          
          //Output the results.
            Console.Write("12345 factors into: ");
            int j;
            for (j = 0; j<results.Length;j++){
                  if (j == results.Length - 1)
                      Console.WriteLine(results[j]);
                  else 
                      Console.Write(results[j] + ", ");
            }
        }
}
[Visual Basic]Imports System
Imports System.Runtime.Remoting.Messaging
Imports MyFactorize     ' Proxy class namespace

Public Class TestCallback
      Public Shared Sub Main()
            Dim factorizableNum As Long = 12345
            Dim pf As PrimeFactorizer = new PrimeFactorizer()

          ' Begin the Async call to Factorize.
            Dim ar As IAsyncResult = pf.BeginFactorize(factorizableNum, Nothing, Nothing)

          ' Wait for the asynchronous operation to complete.
      ar.AsyncWaitHandle.WaitOne()

          ' Get the completed results.
          Dim results() as Long
          results = pf.EndFactorize(ar)
          
          'Output the results.
            Console.Write("12345 factors into: ")
            Dim j as Integer
            For j = 0 To results.Length - 1
                  If  j = (results.Length - 1) Then
                      Console.WriteLine(results(j) )
                  Else 
                      Console.Write(results(j).ToString + ", ")
                    End If
          Next j         
      End Sub
End Class

Note that if FactorizeCallback is a context-bound class that requires synchronized/thread-affinity context, the call back is dispatched through the context dispatcher infrastructure. In other words, the call back might execute asynchronously with respect to its caller for such contexts. That is precisely the semantics of the one-way qualifier on method signatures. It means that any such method call might execute synchronously or asynchronously, with respect to caller, and the caller cannot make any assumptions about completion of such a call when execution control returns to it.

Also, calling EndInvoke before the asynchronous operation is complete will block the caller. The behavior for calling it a second time with the same AsyncResult is undefined.

The Cancel method is a request to cancel processing of the method after the specified time-out period has elapsed. Note that it is a request by the client, and that it is recommended that the server honor it. The client should not necessarily make assumptions about whether the server has stopped processing the method after it receives the message that the method has been canceled. It is recommended that the client not destroy resources, such as file objects, as the server might still be using them. The IsCompleted property of the IAsyncResult instance will be set to true after the server has finished its processing and will no longer use any resources supplied by the client. Thus, the client can confidently destroy the resources after the IsCompleted property is set to true.

See Also

Building XML Web Service Clients | Discovering XML Web Services | Creating Clients for XML Web Services | Exploring Existing XML Web Services Created Using ASP.NET | Accessing XML Web Services from a Browser