2011-02-23 17 views
15

Dos preguntas sobre el patrón de devolución de llamada con AsyncCallback y IAsyncResult.Dos preguntas sobre el patrón AsyncCallback y IAsyncResult

me cambió la pregunta con un ejemplo de código:

using System; 
using System.Collections.Generic; 
using System.Text; 

namespace TestAsync 
{ 
    class Program 
    { 
     private static Wrapper test = new Wrapper(); 

     static void Main(string[] args) 
     { 
      test.BeginMethod("parameter 1", "parameter 2", Callback); 
      Console.ReadKey(); 
     } 

     private static void Callback(IAsyncResult ar) 
     { 
      string result = test.EndMethod(ar); 
     } 
    } 

    public interface ITest 
    { 
     IAsyncResult BeginMethod(string s1, string s2, AsyncCallback cb, object state); 
     string EndMethod(IAsyncResult result); 
    } 

    public class Wrapper 
    { 
     private ITest proxy = new Test(); 

     public void BeginMethod(string s1, string s2, AsyncCallback cb) 
     { 
      proxy.BeginMethod(s1, s2, cb, proxy); 
     } 

     public string EndMethod(IAsyncResult result) 
     { 
      return ((ITest)(result.AsyncState)).EndMethod(result); 
     } 
    } 

    public class Test : ITest 
    { 
     private string WorkerFunction(string a, string b) 
     { 
      // "long running work" 
      return a + "|" + b; 
     } 

     public IAsyncResult BeginMethod(string s1, string s2, AsyncCallback cb, object state) 
     { 
      Func<string, string, string> function = new Func<string, string, string>(WorkerFunction); 
      IAsyncResult result = function.BeginInvoke(s1, s2, cb, state); 
      return result; 
     } 

     public string EndMethod(IAsyncResult result) 
     { 
      return (string)(result.AsyncState); 
     } 
    } 

    public delegate TResult Func<T1, T2, TResult>(T1 t1, T2 t2); 
} 

COMENZAR EDITAR
estoy empezando a ver lo que está pasando. He mezclado un patrón asíncrono WCF y un patrón asíncrono normal. En WCF uno usa un proxy y Begin- and EndMethod debe pasarse el proxy y no la función delegar. En el caso de WCF, el casting funciona, en el caso normal no. WCF usa el atributo [OperationContract (AsyncPattern = true)] probablemente para imponer un patrón algo diferente. FIN EDITAR

¿Por qué el error en la línea return (string)(result.AsyncState);?
Exactamente el mismo patrón en el código de producción está bien.

En segundo lugar, ¿por qué no puedo depurar el código en BeginMethod of class Test?
Solo puedo romper en WorkerFunction.

Respuesta

27

Déjeme darle este código de muestra para aclarar un poco las cosas. Por favor, cree una nueva aplicación de consola y utilice esta

public class Test 
{ 
    private int WorkerFunction(string a, string b) 
    { 
     //this is the guy that is supposed to do the long running work 
     Console.WriteLine(a); 
     Console.WriteLine(b); 
     return a.Length + b.Length; 
    } 

    private void MyCallBack(IAsyncResult ar) 
    { 
     Func<string, string, int> function = ar.AsyncState as Func<string, string, int>; 
     int result = function.EndInvoke(ar); 
     Console.WriteLine("Result is {0}", result); 
    } 
    public void CallMethod() 
    { 
     Func<string, string, int> function = new Func<string, string, int>(WorkerFunction); 
     IAsyncResult result = function.BeginInvoke("param1", "param2", MyCallBack, function); 
    } 


} 

class Program 
{ 

    static void Main(string[] args) 
    { 
     Test test = new Test(); 
     test.CallMethod(); 
    } 
} 

Como se puede ver la función de devolución de llamada (MyCallBack) recibe un objeto IAsyncResult pasa de nuevo a ella. Es este objeto IAsynchResult cuyo AyncState le proporciona el objeto original que había pasado en la llamada al método BeginInvoke. En este caso (y como práctica general) se pasa el delegado como el objeto (que era la variable llamada "función"). Se llamó a la devolución de llamada, luego obtuve el objeto delegado original devolviendo el ar.AsyncState, luego llamé a EndInvoke para obtener el resultado.

En cuanto a que no se golpee el punto de interrupción, me temo que necesito más información al respecto. Qué quieres decir exactamente? ¿Dónde está esta declaración Console.WriteLine?

NUEVA RESPUESTA OK aquí está mi versión de su código. Básicamente, no importa desde dónde llame a EndInvoke, debe llamarlo al objeto delegado real (en su caso, la variable "función" que instancia, pasándole el objeto IAsyncResult real). El código que tiene trata de enmascarar esta instalación, pero debo decir que hay formas menos complicadas de hacerlo. Estaré más que feliz de escribir una especie de envoltorio para usted si lo desea. Por ahora simplemente le devuelvo el código con mi pequeña adición, eso debería hacerlo funcionar. Dado que estás usando variables de nivel de clase, por lo tanto, estoy obligado a usar una de ellas. Esto no es realmente seguro para subprocesos en este momento. Pero aquí va

using System; 
using System.Collections.Generic; 
using System.Text; 

namespace TestAsync 
{ 
    class Program 
    { 
     private static Wrapper test = new Wrapper(); 

     static void Main(string[] args) 
     { 
      var objectState = new object(); 
      test.BeginMethod("parameter 1", "parameter 2", Callback, objectState); 
      Console.ReadKey(); 
     } 

     private static void Callback(IAsyncResult ar) 
     { 
      string result = test.EndMethod(ar); 
      Console.WriteLine(result); 
     } 
    } 

    public interface ITest 
    { 
     IAsyncResult BeginMethod(string s1, string s2, AsyncCallback cb, object state); 
     string EndMethod(IAsyncResult result); 
    } 

    public class Wrapper 
    { 
     private ITest proxy = new Test(); 

     public void BeginMethod(string s1, string s2, AsyncCallback cb) 
     { 
      proxy.BeginMethod(s1, s2, cb, proxy); 
     } 

     public string EndMethod(IAsyncResult result) 
     { 
      return ((ITest)(result.AsyncState)).EndMethod(result); 
     } 
    } 

    public class Test : ITest 
    { 
     Func<string, string, string> _delgateObject; 
     private string WorkerFunction(string a, string b) 
     { 
      // "long running work" 
      return a + "|" + b; 
     } 

     public IAsyncResult BeginMethod(string s1, string s2, AsyncCallback cb, object state) 
     { 
      Func<string, string, string> function = new Func<string, string, string>(WorkerFunction); 
      this._delgateObject = function; 
      IAsyncResult result = function.BeginInvoke(s1, s2, cb, state); 
      return result; 
     } 

     public string EndMethod(IAsyncResult result) 
     { 
      var test = result.AsyncState; 
      return this._delgateObject.EndInvoke(result); 
     } 
    } 

    public delegate TResult Func<T1, T2, TResult>(T1 t1, T2 t2); 
} 
+0

Usted completa la función de delegado como el parámetro de estado 'object @ object' - ¿sabe si eso es obligatorio? – Gerard

+0

En lugar de 'IAsyncResult result = function.BeginInvoke (...' podría simplemente hacer 'function.BeginInvoke (...'? – Gerard

+0

Sí, siento que CallBack debería ser WorkerFunction. Voy a cambiar esto ahora. No es obligatorio rellene la función de delegado como el parámetro de estado @object. Sin embargo, como puede ver haciendo eso le da acceso al delegado dentro del cuerpo de la devolución de llamada. Debe llamar a un EndInvoke en el delegado desde la devolución de llamada para que necesite el delegado función de referencia. Y sí, simplemente puede hacer funcionar.BeginInvoke. Mantuve la muestra con la misma sintaxis que estaba usando realmente. Como puede ver, no reutilizo la variable "resultado" después de que el BeginInvoke – Nikhil

2

This article me ayudó a entender lo que estaba pasando. Wcf's OperationContract implementa un patrón Async especial, que llama sincrónicamente a [Operation] en un hilo separado. Begin [Operation] y End [Operation] se utilizan para crear el patrón, pero no se invocarán realmente. Por lo tanto, este patrón con sus firmas y atributos parece ser idéntico al hacer una llamada síncrona al cliente a través de, p. Ej. un BackgroundWorker.

Solo puede establecer AsyncPattern [del atributo OperationContract] en true en un método con una firma compatible con BeginOperation, y el contrato de definición también debe tener un método coincidente con una firma compatible con EndOperation. Estos requisitos se verifican en el tiempo de carga del proxy.Lo que AsyncPattern hace es vincular el método síncrono subyacente con el par Begin/End y correlaciona la ejecución síncrona con la asíncrona. En resumen, cuando el cliente invoca un método del formulario BeginOperation con AsyncPattern establecido en verdadero, le dice a WCF que no intente invocar directamente un método con ese nombre en el servicio. En cambio, utilizará un hilo del grupo de subprocesos para llamar de forma síncrona al método subyacente (identificado por el nombre de la Acción). La llamada síncrona bloqueará el hilo del grupo de subprocesos, no del cliente llamante. El cliente solo será bloqueado por el menor momento que tarde despachar la solicitud de llamada al grupo de subprocesos. El método de respuesta de la invocación síncrona se correlaciona con el método EndOperation.