Cualquier cosa que valga la pena se hace en equipo

Cualquier cosa que valga la pena se hace en equipo

domingo, 23 de enero de 2011




Manual del

Lenguaje de programación

C#

Parte V-A


Programación con hilos múltiples (Threads) y uso de Patrones de Diseño



OBJETIVO DEL MANUAL 3

Un motor de procesos batch 3

La Interfaz Comando 4

Las clases que implementan la interfaz Comando 4

La clase miClase 4

La clase miOtraClase 6

El motor batch de tareas asíncronas 8

Un programa que utiliza el motor batch 10

Una mejor versión del motor de procesos batch 12

La Interfaz de Comandos 12

La clase abstracta AbstractComando 15

Una mejora en el motor batch de tareas asincrónicas 16




OBJETIVO DEL MANUAL


Es intención en este manual presentar un modelo de implementación de threads así como la implementación de dos patrones de desarrollo clásicos: Command y Strategy.-

Para eso vamos a plantearnos un motor batch , es decir un proceso que sea capaz de disparar procesos batch (no interactivos ) en diferentes hilos de ejecucion


Un motor de procesos batch

Un motor de procesos batch es un software que es capaz de ejecutar procesos batch de modo concurrente y controlado.-

Lo que expondremos acá es un modelo simple del mismo del que después pueda derivarse un motor de uso profesional.-

Lo importante del motor desde el punto de vista de la arquitectura es que se trata de una aplicación que es capaz de manejar múltiples hilos de ejecución, colocar en cada uno de esos hilos un proceso a ejecutar , lanzar dichos procesos , esperar su conclusión y reportar que el proceso finalizo.-

Lo importante del motor desde el punto de vista de la técnica de diseño es que cumple con un patrón de los enunciados por Gamma y otros (GoF []). Este patrón se denomina Command (Comando).-

La idea de Comando como patrón de nuestro diseño es un objeto que tenga 3 estadios clásicos: Inicio, ejecución y cierre. Llamaremos a esos métodos como Inicio(), run() y CleanUp().-



La Interfaz Comando


using System;

using System.Collections.Generic;

using System.Text;


namespace ThreadPoolTest_5

{


public
interface
Comando

{


void inicio();


void run();


void cleanup();

}



}


Las clases que implementan la interfaz Comando

Veamos ahora clases concretas de la Interfaz comando. Estas clases implementan los métodos declarados en la interfaz.-

Daremos dos implementaciones una llamada miClase y la otra llamada miOtraClase.-

No tienen nada de particular salvo que ambas implementan a la interfaz Comando.-

La clase miClase


using System;

using System.Collections.Generic;

using System.Text;

using System.Threading;


namespace ThreadPoolTest_5

{

public class miClase : Comando

{

private string _nombre;

private bool _terminado;


public string nombre

{

get { return _nombre; }

set { _nombre = value; }

}


public bool terminado

{

get { return _terminado; }

set { _terminado = value; }

}


public void inicio()

{

_terminado = false;

}




public void run()

{

Console.WriteLine(this.GetType().ToString() + " :Procesando requerimiento '{0}'." +

" Hash: {1}",

(string)nombre, Thread.CurrentThread.GetHashCode());



// Simulacion de tiempo de procesamiento

int ticks = Environment.TickCount;

//Trabajar mientras no hayan transcurrido los 2 segundos

while (Environment.TickCount - ticks < 2000) ;


Console.WriteLine("Requerimiento '{0}' procesado",

(string)nombre);

}


public void cleanup()

{

_terminado = true;

}


}//clase

}//namespace


La clase miOtraClase


using System;

using System.Collections.Generic;

using System.Text;

using System.Threading;



namespace ThreadPoolTest_5

{

public class miOtraClase : Comando

{

private string _nombre;

private bool _terminado;


public string nombre

{

get { return _nombre; }

set { _nombre = value; }

}


public bool terminado

{

get { return _terminado; }

set { _terminado = value; }

}


public void inicio()

{

_terminado = false;

}



public void run()

{

Console.WriteLine(this.GetType().ToString() +": Procesando requerimiento '{0}'." +

" Hash: {1}",

(string)nombre, Thread.CurrentThread.GetHashCode());



//Calcular

CalcularCosenos();


Console.WriteLine("Requerimiento '{0}' procesado",

(string)nombre);

}


private void CalcularCosenos()

{


const double EPSILON = 0.000000001;

const double DESDE = 0.5346222;

const double HASTA = 3.1415926;

const double PASO = 0.000001999;


double lRadianes = DESDE;

double RadCuadrado;

double s;

double t;

double dFabsLRas;

double dFabsProd;

int k;

string Aux = "";



while (lRadianes < HASTA)

{

k = 0;

t = 1;

s = 1;

RadCuadrado = lRadianes * lRadianes;

dFabsLRas = Math.Abs(t);

dFabsProd = (EPSILON * Math.Abs(s));


while (dFabsLRas > dFabsProd)

{

k = k + 2;

t = (-1) * t * RadCuadrado / (k * (k - 1));


// 1 - x^2/2! ...

s = s + t;

dFabsProd = (EPSILON * Math.Abs(s));

dFabsLRas = Math.Abs(t);

}


Aux = lRadianes.ToString() + ":" + s.ToString();

//Mostrar Aux


lRadianes += PASO;

}





}

public void cleanup()

{

_terminado = true;

}

}

}


El motor batch de tareas asíncronas

Ahora vamos a implementar el motor.-

El motor de tareas asíncronas representa una implementación de un modelo de procesamiento al que también llamamos monitor.

Se trata de un software capaz de disparar OTRAS tareas según una configuración de tiempos.-

En el ejemplo estamos utilizando 4 conceptos sobre los que el motor esta diseñado:

  1. Para que el motor deba ejecutar un proceso en hilos (threads) múltiples usaremos un namespace llamado System.Threading en el que encontramos una clase llamada WaitCallback que es la implementación de un prototipo de función CallBack.-
  2. La clase WaitCallback, necesita en su constructor un método que cumpla con este prototipo :


private
static
void MiFuncion(object miObjeto);

De modo que nosotros creamos un método dentro de nuestra clase de implementación del motor que pueda cumplir con este prototipo.-

  1. La clase ThreadPool, con métodos estáticos públicos es la que nos permitirá levantar cada uno de los procesos que el motor batch dispara. ThreadPool nos permite definir la cantidad de threads que se abrirán en la sesión así como encolar los procesos que disparamos de modo de ejecutarlos secuencialmente.-
  2. EL motor tiene un método llamado Trabaja() que es el que ejecuta la parte central de la tarea. Ahora bien : ¿ Que necesita Trabaja() para hacer su tarea ?.

Necesita una lista de objetos. De alli que Trabaja() tiene como parámetro un contenedor genérico <List> de objetos a procesar.-


using System;

using System.Collections.Generic;

using System.Text;

using System.Threading;



namespace ThreadPoolTest_5

{

class MotorBatch

{


public static void trabaja(List<object> cm)

{


//EL motor tiene su funcion de disparo y arma un

// handler a ella

WaitCallback callBack;

callBack = new WaitCallback(FuncionAEjecutar);



//El motor ajusta la cantidad de threads por procesador

int minWorker, minIOC;

ThreadPool.GetMinThreads(out minWorker, out minIOC);

// 4 threads asincronicos como minimo, por procesador

ThreadPool.SetMinThreads(4, minIOC);


//El motor lanza las tareas

foreach(object com in cm)

{

ThreadPool.QueueUserWorkItem(callBack, com);

}


//El motor sincroniza el pool de threads

//Hasta terminar

Console.ReadLine();

}


private static void FuncionAEjecutar(object mc)

{

Comando xco = (Comando)mc;

xco.inicio();

xco.run();

xco.cleanup();

}


}//clase


}//namespace


Un programa que utiliza el motor batch

Corresponde ahora que hagamos un programa que utiliza el motor batch para dispara procesos. Lo que haremos será que el motor dispare las implementaciones de Comando que hicimos con miClase y miOtraClase.-

Observemos que es lo que nuestro programa hace ahora:

Siendo que el motor necesita que a su método publico y estático Trabaja() se le pase como parámetro un genérico de tipo <List> con objetos, lo primero que hace la aplicación que llamara al motor es generar una lista de dicho tipo.

Podemos ir viendo ya que esta generación es una tarea que debería estar controlada en el sentido que no podemos colocar cualquier tipo de objetos en esa lista sino solamente aquellos que deriven de Comando. Ese punto lo revisaremos en las mejoras que haremos al motor batch.-

using System;

using System.Threading;

using System.Collections.Generic;


namespace ThreadPoolTest_5

{


//

class MainApp

{

static void Main()

{

//Alguien carga los comandos en una lista y el motor

//la invoca

List<object> cm = ListaDeComandos();


MotorBatch.trabaja(cm);


}


static public List<object> ListaDeComandos()

{

miClase mc;

miOtraClase moc;

List<object> comandos = new List<object>();


for (int i = 0; i < 10; i++)

{

if ((i % 2) != 0)

{

mc = new miClase();

mc.nombre = "Hilo nro: " + i;

comandos.Add(mc);

}

else

{

moc = new miOtraClase();

moc.nombre = "Hilo nro: " + i;

comandos.Add(moc);

}

}


return comandos;

}//


}

}



Una mejor versión del motor de procesos batch

La Interfaz de Comandos

Si hacemos un análisis más meticuloso de la estructura que tendrá el motor batch, vemos que el diseño no solamente se adecua al uso de una interfaz (que es la representación de una plantilla con métodos comunes para polimorfismo) sino que podemos tener una clase abstracta con implementaciones de dichos métodos comunes.-

Estas implementaciones en algunos de los casos serán luego sobrescritas por las implementaciones de la clase abstracta y en otros casos serán implementados ya que algunos métodos los declararemos también como abstractos.-



Usando la utilidad View Class Diagram de Visual Studio, podemos ver las relaciones entre la Interfaz (y su declaración de métodos) con la clase abstracta y una clase (miClase) que implementa a esta ultima.-

Esta relación entre Interfaz-Clase Abstracta-Clase Concreta es un ejemplo de cómo debemos diseñar este tipo de problemas de ingeniería:

  1. Un problema requiere de la confección de un algoritmo que ejecuta acciones de objetos que tienen métodos comunes, pero se instancian de CLASES DISTINTAS. En nuestro caso el problema es la ejecución de los métodos de un comando.-
  2. Esto nos lleva a la idea de polimorfismo. Para eso entonces diseñamos la interfaz y pensamos los métodos comunes.-
  3. Existen además métodos que admiten cierta implementación o directamente necesitan de ella. Otros métodos, en cambio, deben ser definido para cada clase concreta
  4. Esto nos lleva a la idea de clase abstracta y métodos virtuales. La clase será la base de otras que heredaran su comportamiento pero lo especializaran.-



  1. El diseño de una clase abstracta es una tarea de pasos sucesivos. No es el primer eslabón de la cadena sino algún punto de culminación ya que implica que hemos visualizado un patrón de comportamiento del sistema y que hay tareas que sabemos que son comunes.-
  2. Si hemos llegado a ese punto del diseño, podremos diferenciar entre aquellos métodos que serán implementados en la clase abstracta y posiblemente ampliados en la subclase, y aquellos otros métodos a los que NECESARIAMNETE implementara la subclase.

    En nuestro caso, Inicio() y YaTermino() por ejemplo, han sido implementadas en la interfaz, en tanto presentan una lógica que entendemos común a todos los derivados.

public virtual void inicio()

{

_terminado = false;

}


public bool YaTermino()

{

return (_terminado == true);

}


EN cambio run() que es el proceso de ejecución en si de la clase, será necesariamente implementado por la subclase. De allí que coloquemos a este ultimo método como abstract.


public abstract void run();


La clase abstracta AbstractComando

Dentro del archivo donde declaramos la Interfaz Comando, colocaremos una clase Abstracta que contenga los métodos básicos y una implementación mínima de los mismos.-

Llamamos a esta clase AbstractComando


using System;

using System.Collections.Generic;

using System.Text;


namespace threadPoolTest_6

{

public interface Comando

{

void inicio();

void run();

void cleanup();

bool YaTermino();

String elNombre();

}



public abstract class AbstactComando:Comando

{

public static string CLASE_BASE = "threadPoolTest_6.AbstactComando";

private string _nombre;

private bool _terminado;


public string nombre

{

get { return _nombre; }

set { _nombre = value; }

}


public String elNombre()

{

return _nombre;

}


public virtual void inicio()

{

_terminado = false;

}


public bool YaTermino()

{

return (_terminado == true);

}


public abstract void run();



public virtual void cleanup()

{

_terminado = true;

}

}

}


Una mejora en el motor batch de tareas asincrónicas


using System;

using System.Collections.Generic;

using System.Collections;

using System.Text;

using System.Threading;

using System.Reflection;



namespace threadPoolTest_6

{

public class MotorBatch

{


public static void trabaja(List<object> cm)

{

//EL motor tiene su funcion de disparo y arma un

// handler a ella

WaitCallback callBack;

callBack = new WaitCallback(FuncionAEjecutar);


//El motor ajusta la cantidad de threads por procesador

int minWorker, minIOC;

ThreadPool.GetMinThreads(out minWorker, out minIOC);


// 4 threads asincronicos como minimo, por procesador

ThreadPool.SetMinThreads(4, minIOC);



//El motor lanza las tareas

foreach(object com in cm)

{

ThreadPool.QueueUserWorkItem(callBack, com);

}


//El motor sincroniza la finalizacion el pool de threads

do

{

foreach (object com in cm)

{

Comando xco = (Comando)com;

if (xco.YaTermino())

{


//TODO: Logger

Console.WriteLine("-> Comando '{0}' procesado. Restan '{1}'", xco.elNombre(), cm.Count);

cm.Remove(xco);

break;

}

}

} while (cm.Count > 0);


//Hasta terminar

//TODO:Logger

Console.WriteLine("Motor Batch Termino");

}


//Esta es la funcion a la que se apunta en el callback

//llama a los 3 metodos de un comando

private static void FuncionAEjecutar(object mc)

{

Comando xco = (Comando)mc;

xco.inicio();

xco.run();

xco.cleanup();

}


}//clase


public class unPar

{

String _nombreClase;

String _nombreYPasoAssembly;


public String nombreYPasoAssembly

{

get { return _nombreYPasoAssembly; }

set { _nombreYPasoAssembly = value;}

}


public String nombreClase

{

get { return _nombreClase; }

set { _nombreClase = value; }

}//-


public unPar(String nombreClase, String nombreYPasoAssembly)

{

_nombreClase = nombreClase;

_nombreYPasoAssembly = nombreYPasoAssembly;

}//-


}//clase


public class ParAssemblyClase

{


static List <unPar> lPar = null;


public static void Agregar(String nombreClase,String nombreYPasoAssembly)

{

if (lPar == null)

lPar = new List<unPar>();


unPar p = new unPar(nombreClase,nombreYPasoAssembly);

lPar.Add(p);

}//


public static List<unPar> TraerLista()

{

return lPar;

}//



//Retornar una lista de comandos a partir de una lista

// de pares Assembly-clase que se busca cargado en la misma sesion

static public List<object> ListaComandos()

{

List<object> comandos = null;


if (lPar != null)

comandos= ListaComandos(lPar);


return comandos;

}



//Retornar una lista de comandos a partir de una lista

// de pares Assembly-clase que se recibe

static public List<object> ListaComandos(List<unPar> tabla)

{


Assembly assem;

Object o;


List<object> comandos = new List<object>();


IEnumerator i = tabla.GetEnumerator();

unPar m;


while (i.MoveNext())

{

m = (unPar)i.Current;


try

{

assem = Assembly.LoadFrom(m.nombreYPasoAssembly);


//Los constructores de las clases que implementan

//Comando no tienen parametros. De alli el parametro

//new Object[] { }

o = assem.CreateInstance(m.nombreClase, false,

BindingFlags.ExactBinding, null, new Object[] { }, null, null);

//TODO: Logger

Console.WriteLine("Clase Base de {0} : {1}",m.nombreClase, o.GetType().BaseType.ToString());


//solo los que extienden AbstractComando son considerados

//procesos a aejecutar

if (o.GetType().BaseType.ToString().Equals( AbstactComando.CLASE_BASE))

comandos.Add(o);

}

catch (Exception ex)

{ //TODO: Logger

Console.WriteLine("Excepcion " + ex.StackTrace);

}


}


return comandos;


}//-

}//clase


}//namespace


No hay comentarios: