Comunicar con servicios Web XML de forma asincrónica

La comunicación asincrónica con un servicio Web XML sigue el modelo de diseño asincrónico que se utiliza en el resto de Microsoft .NET Framework. Sin embargo, en primer lugar es importante observar que no es necesario programar un servicio Web XML específico para controlar solicitudes asincrónicas que se llamen de forma asincrónica. La clase de proxy que crea para el cliente con la herramienta Lenguaje de descripción de servicios Web (Wsdl.exe) crea automáticamente métodos para llamar al método de servicio Web XML de forma asincrónica. Esto ocurre incluso si sólo hay una implementación sincrónica del método de servicio Web XML.

Modelo de diseño de llamadas a métodos asincrónicos de .NET Framework

El modelo de diseño para llamar a métodos de forma asincrónica, especificado en .NET Framework, establece que hay dos métodos asincrónicos por cada método sincrónico. Para cada método sincrónico, hay un método asincrónico Begin y un método asincrónico End. El cliente llama al método Begin para iniciar o comenzar la llamada de método. Es decir, el cliente ordena al método que comience a procesar la llamada de método, pero que regrese inmediatamente. El cliente llama al método End para obtener los resultados del procesamiento realizado por la llamada de método de servicio Web XML.

¿Cómo determina un cliente cuándo debe llamar al método End? Hay dos métodos de implementar en un cliente la forma de determinarlo, como se define en .NET Framework. El primero consiste en pasar una función de devolución de llamada al método Begin y llamarla cuando finaliza el procesamiento del método. Mientras que el segundo utiliza uno de los métodos de la clase WaitHandle para hacer que el cliente espere a que finalice el método. Si un cliente implementa el segundo método y llama al método Begin, el valor devuelto no es el tipo de datos especificado en el método de servicio Web XML, sino un tipo que implementa la interfaz IAsyncResult. La interfaz IAsyncResult contiene una propiedad AsyncWaitHandle de tipo WaitHandle, que implementa métodos que permiten esperar a que se señalicen los objetos de sincronización mediante WaitHandle.WaitOne, WaitAny y WaitAll. La señalización de un objeto de sincronización significa que los subprocesos que están a la espera del recurso especificado pueden tener acceso al mismo. Si un cliente de servicios Web XML utiliza el método de espera para llamar de forma asincrónica a un solo método de servicio Web XML, puede llamar a WaitOne para esperar a que finalice el procesamiento del método de servicio Web XML.

Es importante observar que, con independencia de cuál de los dos métodos elija un cliente para comunicarse con un servicio Web XML de forma asincrónica, los mensajes SOAP enviados y recibidos son idénticos a los que se producen cuando la comunicación se realiza sincrónicamente. Es decir, sólo se envía una solicitud SOAP y sólo se recibe una respuesta SOAP a través de la red. Esto se consigue en la clase de proxy al controlar la respuesta SOAP mediante un subproceso diferente del que utilizó el cliente para llamar al método Begin. Por lo tanto, el cliente puede continuar realizando otros trabajos en su subproceso, al tiempo que la clase de proxy controla la recepción y el procesamiento de la respuesta SOAP.

Implementar un cliente de servicios Web XML que realice una llamada asincrónica de método

La infraestructura para realizar una llamada asincrónica desde un cliente de servicios Web XML creado mediante ASP.NET a un servicio Web XML está integrada en .NET Framework y en la clase de proxy creada por la herramienta Lenguaje de descripción de servicios Web (Wsdl.exe). El modelo de diseño para realizar llamadas asincrónicas se define en .NET Framework y la clase de proxy proporciona el mecanismo para comunicarse de forma asincrónica con un método de servicio Web XML. Cuando se crea una clase de proxy para un servicio Web XML mediante Wsdl.exe, se generan tres métodos por cada método de servicio Web XML público del servicio. En la tabla siguiente se describen los tres métodos.

Nombre del método en la clase de proxy Descripción
<nombreDeMétodoDeServicioWeb> Envía un mensaje para el método de servicio Web XML denominado <nombreDeMétodoDeServicioWeb> de forma sincrónica.
Begin<nombreDeMétodoDeServicioWeb> Inicia la comunicación asincrónica de mensajes con un método de servicio Web XML denominado <nombreDeMétodoDeServicioWeb>.
End<nombreDeMétodoDeServicioWeb> Finaliza una comunicación asincrónica de mensajes con un método de servicio Web XML denominado <nombreDeMétodoDeServicioWeb> y obtiene el mensaje finalizado del método de servicio Web XML.

El ejemplo de código siguiente contiene un método de servicio Web XML cuyo procesamiento puede tardar un tiempo relativamente prolongado en finalizar. Es un buen ejemplo de cuándo se debe configurar el cliente de servicios Web XML para llamar al método de servicio Web XML de forma asincrónica.

<%@ 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

El ejemplo de código siguiente es un fragmento de una clase de proxy generada por la herramienta Wsdl.exe para el método de servicio Web XML anterior. Observe que los métodos BeginFactorize y EndFactorize se utilizan para comunicarse de forma asincrónica con el método de servicio Web XML Factorize.

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]));
        }
    }

Hay dos métodos para comunicarse de forma asincrónica con un método de servicio Web XML. En el ejemplo de código siguiente se muestra la comunicación asincrónica con un método de servicio Web XML y el uso de una función de devolución de llamada para recuperar los resultados del método de servicio Web XML.

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

En el ejemplo de código siguiente se demuestra la comunicación asincrónica con un método de servicio Web XML y, a continuación, el uso de un objeto de sincronización para esperar la finalización del procesamiento.

// -----------------------------------------------------------------------// 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

Observe que si FactorizeCallback es una clase enlazada a un contexto que requiere un contexto sincronizado o de afinidad de subprocesos, la función de devolución de llamada se enviará a través de la infraestructura del distribuidor de contextos. Es decir, la devolución de llamada se puede ejecutar de forma asincrónica con respecto al llamador para estos contextos. Precisamente, ésta es la semántica de un calificador unidireccional de firmas de métodos. Esto significa que las llamadas al método se pueden ejecutar de forma sincrónica o asincrónica con respecto al llamador y el llamador no puede suponer nada acerca de la finalización de este tipo de llamada cuando se devuelve el control de la ejecución.

Además, si se llama a EndInvoke antes de completar la operación asincrónica, se bloqueará el llamador. El resultado de una segunda llamada con el mismo AsyncResult no está definido.

El método Cancel es una solicitud para cancelar el procesamiento del método, una vez transcurrido el tiempo de espera especificado. Observe que es una solicitud del cliente y que se recomienda que el servidor la tenga en cuenta. El cliente no debe suponer necesariamente que el servidor ha dejado de procesar el método después de recibir el mensaje que indica que el método se ha cancelado. Se recomienda que el cliente no destruya recursos, por ejemplo objetos file, ya que se pueden estar utilizando en el servidor. El valor de la propiedad IsCompleted de la instancia IAsyncResult se establecerá a true después de que el servidor finalice el procesamiento y deje de utilizar los recursos suministrados por el cliente. De este modo, el cliente puede destruir los recursos de forma segura, una vez que el valor de la propiedad IsCompleted está establecido a true.

Vea también

Generar clientes de servicios Web XML | Descubrir servicios Web XML | Crear clientes para servicios Web XML | Explorar servicios Web XML existentes creados mediante ASP.NET | Acceso a servicios Web XML desde un explorador