lunes, 2 de marzo de 2015

De los delegados a la lambdas 2: Rarezas

Vamos a continuar la serie dedicada a delegados y lambdas. Después de haber explicado los delegados y antes  de meternos con las lambdas, vamos a ver algunos aspectos "raros" o mejor dicho, menos conocidos de los delegados.

Delegate multicasting


Se trata de una característica que nos va a permitir almacenar más de un método en un delegado.
Para emplearla debemos utilizar el operador +=

   1:  class Program
   2:  {
   3:      delegate void delSaludo();
   4:   
   5:      static void Main(string[] args)
   6:      {
   7:          delSaludo miDel = Saludo1;
   8:          miDel += Saludo2;
   9:   
  10:          miDel();
  11:      }
  12:   
  13:      protected static void Saludo1()
  14:      {
  15:          Console.WriteLine("Este es el saludo 1");
  16:      }
  17:   
  18:      protected static void Saludo2()
  19:      {
  20:          Console.WriteLine("Este es el saludo 2");
  21:      }
  22:  }

La salida por pantalla correspondiente la podéis imaginar:
 
 
 
También podemos utilizar el operador -=
 
Hemos visto un ejemplo con métodos que no devuelven nada. Sin embargo he leído por ahí que si hacemos multicasting a métodos no void obtendremos un excepción. ¿Es esto cierto? Veámoslo
 
   1:  class Program
   2:  {
   3:      delegate int delOperacion(int a, int b);
   4:   
   5:      static void Main(string[] args)
   6:      {        
   7:          delOperacion miDelOp = Suma;
   8:          miDelOp += Mult;
   9:   
  10:          var res = miDelOp(2, 3);
  11:          Console.WriteLine("Resultado={0}",res);
  12:      }
  13:   
  14:      protected static int Suma(int a, int b)
  15:      {
  16:          return a + b;
  17:      }
  18:   
  19:      protected static int Mult(int a, int b)
  20:      {
  21:          return a * b;
  22:      }
  23:  }
 
¿Qué salida obtendremos?
 
 
 
No hay excepción, y el valor que queda en la variable es el valor retornado por el último método invocado. ¿Curioso no?

Delegate async invocation

 
Los delegados pueden ser ejecutados de modo asíncrono. Esto hará que un nuevo thread sea creado para nosotros y que este nuevo thread sea el que ejecute el método almacenado en el delegado.
 
La forma general de usarlo es la siguiente.
 
       delegado.BeginInvoke(params, asyncCallback,  object)
 
Si no vamos a usar un callback podemos fijarlo a nulo, junto con el último parámetro.
 
Un ejemplo:
 
   1:  class Program
   2:  {
   3:      delegate bool delMakeOperation(int param);
   4:      static void Main(string[] args)
   5:      {
   6:          delMakeOperation d1 = MakeLonggOp;
   7:   
   8:          Console.WriteLine("Main (threadId={0}) va a llamar al delegado", Thread.CurrentThread.ManagedThreadId);
   9:          d1.BeginInvoke(6, null, null);
  10:          Thread.Sleep(1000);
  11:          Console.WriteLine("Main acaba");
  12:      }
  13:   
  14:      protected static bool MakeLonggOp (int param)
  15:      {
  16:          Console.WriteLine("MakeLonggOp empieza (threadId={0})",Thread.CurrentThread.ManagedThreadId);
  17:          Thread.Sleep(param * 1000);
  18:          Console.WriteLine("MakeLonggOp acaba");
  19:              
  20:          return true;
  21:      }
  22:  }
 
La salida:
 
 
 
Como veis hay 2 threads ejecutándose...  sin embargo la llamada asíncrona al delegado nunca termina, ya que mi aplicación de consola finaliza antes. ¿Cómo esperamos por la finalización del delegado? Hay varias formas
 
  • Esperando la finalización asíncrona con EndInvoke
  • Esperando a la finalización asíncrona con WaitHandle 
  • Haciendo pooling ...no me gusta :-(
  • Ejecutando un callback
 
Aquí tenéis código para todas ellas:
 
   1:  class Program
   2:  {
   3:      delegate bool delMakeOperation(int param);
   4:      static void Main(string[] args)
   5:      {
   6:          Espera_EndInvoke();
   7:          Espera_WaitHandle();
   8:          Espera_Pooling();
   9:          Llamando_Callback();
  10:      }
  11:   
  12:      protected static bool MakeLonggOp(int param)
  13:      {
  14:          Console.WriteLine("MakeLonggOp empieza (threadId={0})", Thread.CurrentThread.ManagedThreadId);
  15:          Thread.Sleep(param * 1000);
  16:          Console.WriteLine("MakeLonggOp acaba");
  17:   
  18:          return true;
  19:      }
  20:   
  21:      protected static void Espera_EndInvoke()
  22:      {
  23:          delMakeOperation d1 = MakeLonggOp;
  24:   
  25:          Console.WriteLine("Main (threadId={0}) va a llamar al delegado", Thread.CurrentThread.ManagedThreadId);
  26:          var asyncRes=d1.BeginInvoke(6, null, null);
  27:          var res = d1.EndInvoke(asyncRes); //Esta llamada es bloqueante
  28:   
  29:          Console.WriteLine("Metodo asincrono acaba. Resultado={0}", res);
  30:      }
  31:   
  32:      protected static void Espera_WaitHandle()
  33:      {
  34:          delMakeOperation d1 = MakeLonggOp;
  35:   
  36:          Console.WriteLine("Main (threadId={0}) va a llamar al delegado", Thread.CurrentThread.ManagedThreadId);
  37:          var asyncRes = d1.BeginInvoke(6, null, null);
  38:   
  39:          asyncRes.AsyncWaitHandle.WaitOne();//Esta llamada es bloqueante 
  40:   
  41:          var res = d1.EndInvoke(asyncRes);
  42:   
  43:          Console.WriteLine("Metodo asincrono acaba. Resultado={0}", res);
  44:      }
  45:   
  46:      protected static void Espera_Pooling()
  47:      {
  48:          delMakeOperation d1 = MakeLonggOp;
  49:   
  50:          Console.WriteLine("Main (threadId={0}) va a llamar al delegado", Thread.CurrentThread.ManagedThreadId);
  51:          var asyncRes = d1.BeginInvoke(6, null, null);
  52:   
  53:          while (! asyncRes.IsCompleted)
  54:          {
  55:              Thread.Sleep(250);
  56:              Console.Write(".");
  57:          }
  58:   
  59:          //La ejecucion asincrona acabo
  60:   
  61:          var res = d1.EndInvoke(asyncRes);
  62:          Console.WriteLine("Metodo asincrono acaba. Resultado={0}", res);
  63:      }
  64:   
  65:      protected static void Llamando_Callback()
  66:      {
  67:          delMakeOperation d1 = MakeLonggOp;
  68:   
  69:          Console.WriteLine("Main (threadId={0}) va a llamar al delegado", Thread.CurrentThread.ManagedThreadId);
  70:          var asyncRes = d1.BeginInvoke(6, new AsyncCallback(CallbackMethod), "async state");
  71:   
  72:          Console.ReadLine();
  73:      }
  74:   
  75:      static void CallbackMethod(IAsyncResult ar)
  76:      {
  77:          // Recuperamos el delagdo
  78:          AsyncResult result = (AsyncResult)ar;
  79:          delMakeOperation del = (delMakeOperation)result.AsyncDelegate;
  80:   
  81:          string obj = (string)ar.AsyncState; //Objeto pasado cuando se hizo la llamada asíncrona. Obtendremos "async state" que es lo que se paso
  82:              
  83:          // Call EndInvoke to retrieve the results. 
  84:          var res = del.EndInvoke(ar);
  85:                          
  86:          Console.WriteLine("Metodo asincrono acaba. Resultado={0}", res);
  87:      }
  88:   
  89:  }
 
En todos los casos obtenemos una salida como la siguiente:
 
 
 
Como veis hay muchas formas de esperar por la finalización del método asíncrono. De todos modos hoy en día hay formas más elegantes de hacer programación asíncrona (¿te suena async & await?). Ya lo veremos! Prometo escribir al respecto.
 
Con esto dejamos los delegados, aquí tenéis el código completo.
 
En la siguiente entrada trataremos las lambdas!
 
 Post publicados en esta serie:
 
 
 
 

2 comentarios:

  1. en el multicasting es posible escoger el metodo a ejecutar con el delegado ???

    ResponderEliminar
  2. Hola Alexei, no es posible. Si almacenamos más de un método en un delegado se van a ejecutar todos

    ResponderEliminar