Cualquier cosa que valga la pena se hace en equipo

Cualquier cosa que valga la pena se hace en equipo
Mostrando entradas con la etiqueta Threads. Mostrar todas las entradas
Mostrando entradas con la etiqueta Threads. Mostrar todas las entradas

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


lunes, 26 de abril de 2010

Algo sobre threads en C# - Parte III



OBJETIVO


Veamos ahora 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 interqactivos ) 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 [1]).
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().-

Caulquier implementacion que tengo estas 3 tareas sera apra nostros un comando.
Pensemos entonces en una estrcutura de programacion que nos permita sugerir esta plantilla antes de implementarla. Llamamos a esto Interfaz

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

Esta primera funcion solo consumo tiempo de procesamiento

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;


}


}// de clase


}// de namespace




La clase mi otraclase

Esta segunda funcion calcula el coseno de un angulo en radianes por el metodo de expansion:

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

//Calculamos la expansion hasta que el termino enesimo no sea mayor a un EPSILON DADO
while (dFabsLRas > dFabsProd)
{


k = k + 2;


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


// 1 - x^2/2! + x^4/4! - x^8/8! ...


s = s + t;


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


dFabsLRas = Math.Abs(t);


}


Aux = lRadianes.ToString() + ":" + s.ToString();
Console.WriteLine(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 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].-

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

3- 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.-

4- 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 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 <> 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 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 <> cm = ListaDeComandos();

MotorBatch.trabaja(cm);

}

static public List ListaDeComandos()
{
miClase mc;
miOtraClase moc;
List<> comandos = new List<> ();

for (int i = 0; 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);

}

}//for..


return comandos;

}//

}

}





Vemos en esta sencillo ejemplo las ideas basixas del motor batch y su rapida implementaciom
Dejamos para una IV parte de esta serie una version con mejoras en el diseño del motor
**************************************
[1] GoF es el acrónimo de “Gang of Four” o sea “La banda de los cuatro”. El nombre es una broma que liga la denominación de los 4 autores del libro Pattern Designs (Gamma, Helms, Jhonson y Vlissides)con la denominación de un grupo político del poder chino posterior a la muerte de Mao Tse Tung al que se lo acuso de traición por sus posturas radicalizadas durante la revolución cultural .-

sábado, 24 de abril de 2010

Algo sobre threads en C# - Nota 2

POOL DE THREADS


Un modelo hibrido toma aspectos comunes de cada enfoque

Para evitar este tipo de problemas la solución adoptada en la industria es el uso de brokers (distribuidores de un pool de recursos).-









En esta aproximación al problema, existe un administrador de los pedidos encolados, que se encarga de derivarla al primer thread libre.-


Por supuesto que definir la “habilidad” de este administrador para actuar como un broker no es sencillo.-

Un buen administrador del pool, no debe limitarse a habilitar N threads, ya que quizás N sea un numero conservador (y por ende el procesador será subutilizado) o N es un numero ambicioso y podemos llegar al problema de aumento geométrico de la carga descrito al inicio de este capitulo.-

El broker debe descubrir EN TIEMPO REAL, si existe o no potencia de procesamiento ociosa para decidir si habilita un nuevo thread o espera que alguno se libere.-

La solución en C# con pool de threads



Siendo este un problema común, dentro del Framework de .NET tenemos un namespace que nos brinda un administrador de un pool de threads.-
El namespace se denomina System.Threading y la clase se llama ThreadPool.-
Dentro de esta clase tenemos todos métodos estáticos, lo que permite utilizarlos con llamadas globales.-
Veamos un programa que presenta una aproximación al problema con las herramientas que nos brinda C#.-

using System;



using System.Threading;


//En este ejemplo utilizamos una técnica de threading en la que enviamos
// como parámetro del constructor (WaitCallback) un METODO de la clase
// es decir a diferencia de como se puede implementar este tipo de técnicas en Java
// (que se pasan objetos) acá mandamos el nombre del método (función) que se debe ejecutar


namespace ThreadPoolTest


{


class MainApp


{


static void Main()


{


WaitCallback callBack;


callBack = new WaitCallback(FuncionAEjecutar);


ThreadPool.QueueUserWorkItem(callBack,"Hilo 1");


ThreadPool.QueueUserWorkItem(callBack,"Hilo 2");


ThreadPool.QueueUserWorkItem(callBack,"Hilo 3");


Console.ReadLine();


}


static void FuncionAEjecutar(object state)
{
    Console.WriteLine("Procesando requerimiento '{0}'." +


          " El thread esta dentro del pool ?: {1}, Hash: {2}",


                (string)state, Thread.CurrentThread.IsThreadPoolThread,


       Thread.CurrentThread.GetHashCode());

      // Simulacion de tiempo de procesamiento


      Thread.Sleep(2000);


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


                        (string)state);
}


}


}











Este es un ejemplo interesante que nos muestra cuando puede ser útil en un proceso batch el uso de hilos múltiples.-


Observe que cada llamada a la función se ejecuto en un thread independiente, mientras en otro thread se ejecutaba otra llamada a la función.-

Esto puede parecerse a un programa de comunicaciones que puede atender llamadas multiples.-

Frente a cada llamada levanta un thread, pero cada thread llama a la misma funcion de atencion.-

Lo que hicimos en la función llamada FuncionAEjecutar() fue hacerle perder tiempo (Thread.Sleep) para poder ver en la consola esta “simultaneidad” de procesamiento.-

Observe, como comentamos líneas arriba, que los implementadores de la clase han decidido que todos los métodos sean públicos y estáticos.-

El objetivo es hacer de esto un Singleton, es decir que solo exista una instancia de la clase para todo el proceso o sesión.-

Si bien esto podría haberse hecho con un enfoque mas orientado a objetos (constructor privado, interfaz para implementar las funciones a llamar, polimorfismo para las llamadas de las funciones a colocar en el thread), los implementadores entendieron que no era el mejor camino, al menos en C#.-

Esto nos habla de decisiones de diseño, algo que todos los ingenieros de software deben (o deberían) aprender.-

Hagamos una pequeña variante al ejemplo anterior para ver más claramente la ejecución en hilos separados.

En este caso la función llamada en el primer thread termina antes que el disparo de todos los threads requeridos.-

Pero esto no afecta para nada el enfoque del programa.-

using System;


using System.Threading;


//En este ejemplo utilizamos una tecnica de threading en la que enviamos
// como parametro del constructor (WaitCallback) un METODO de la clase
// es decir a diferencia de como se puede implementar este tipo de tecnicas en Java
// (que se pasan objetos) aca mandamos el nombre del metodo (funcion) que se debe ejecutar


namespace ThreadPoolTest
{


class MainApp
{
static void Main()
{

WaitCallback callBack;
callBack = new WaitCallback(FuncionAEjecutar);
string s;


for (int i=0; i < 8; i++)
{


       s = "Hilo nro: " + i;
      ThreadPool.QueueUserWorkItem(callBack,s);
}


Console.ReadLine();


}


static void FuncionAEjecutar(object state)
{


              Console.WriteLine("Procesando requerimiento '{0}'." + " Hash: {1}",string)state,Thread.CurrentThread.GetHashCode());


               // Simulacion de tiempo de procesamiento


               Thread.Sleep(2000);
               Console.WriteLine("Requerimiento '{0}' procesado", (string)state);
}


}


}














Entendiendo mejor el problema



Ahora bien, el recurso que todos queremos utilizar es el procesador.-


¿Que pasara si la función que se coloca en el thread consume recursos?


Pensemos que nosotros adrede hicimos que nuestra función NO CONSUMIESE RECURSOS de CPU (ese es el sentido de Sleep ¡!) sino que perdiese tiempo.-


Lo que haremos ahora es que nuestra función, en esos 2 segundos (o 2000 milisegundos que es como se lo indicamos a Sleep) trabaje compulsivamente.-


Para eso lo haremos iterar durante esos 2 segundos:






int ticks = Environment.TickCount;


//Trabajar mientras no hayan transcurrido los 2 segundos


while(Environment.TickCount - ticks < 2000);






Es decir la versión de programa ahora presentara un cambio en la función llamada:


using System;


using System.Threading;
namespace ThreadPoolTest


{


 class MainApp


 {
  static void Main()
  {


          WaitCallback callBack;
          callBack = new WaitCallback(FuncionAEjecutar);
          string s;


          for (int i=0; i < 8; i++)
        {
             s = "Hilo nro: " + i;
            ThreadPool.QueueUserWorkItem(callBack,s);
        }


       Console.ReadLine();


}


static void FuncionAEjecutar(object state)
{


    Console.WriteLine("Procesando requerimiento '{0}'." + " Hash: {1}",
     (string)state,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)state);


}


}


}

Observe como el procesador inmediatamente queda sobrecargado (el efecto del while es mantenerlo “entretenido”), y la cantidad de threads se mantiene baja.


Esto es porque a partir del 3er pedido, (hilo 2), los mismos no se procesan hasta que no termine el anterior y libere un thread.-

Esa es la acción inteligente, que esperábamos de un broker que administre el pool de threads.-



 
 
 
 
 
 
 
 
 
 
 
 
Los timers como herramientas en hilos múltiples



En nuestros manuales Curso MFC-II.doc y Curso MFC-III.doc hablamos de los timers de Win 32, utilizados dentro de aplicaciones C++.-
La deficiencia del uso de un CONTROL timer para el control de tiempo, es que se debe reposar en técnicas no seguras para la ejecución del tick del reloj.-
Esto es porque los eventos levantados por este control (o API o clase dependiendo de cómo lo utilice) son sincrónicos respecto de la aplicación que lo ha instanciado.-

Es decir, se debe esperar que la aplicación Windows (es decir hace falta una aplicación con ventanas) pase el control a la ronda de atención de mensajes de Windows y allí sea atendido por el mensaje WM_TIMER.-
Esto significa que si la aplicación esta ocupada en otras tareas y su tiempo de ronda de atención, supera al del evento Elapsed(periodo a controlar) del control Timer, este perderá 1 o mas rondas de ejecución.-


En el Framework .NET existen 3 tipos de Timers.-

Server Based Timers

Uno es el que se ve en la solapa Componentes del Toolbox.-




















Este timer es llamado timer basado en servidor.-


Puede trabajar indistintamente con Windows Forms o con servicios batch (sin interacción con el usuario) y se encuentra en la biblioteca de clases llamada System.Timers.Timer

Esta diseñado para trabajar con hilos multiples y en los hilos llamados “de trabajo” (worker threads) que son aquellos que no atienden servicios de interfaz con el usuario.-

Su arquitectura difiere de la anterior, al punto que son más exactos que los Windows timers.-

Pero la diferencia esencial es que el mismo timer puede manejar un evento levantado en OTRO THREAD.-



Windows Timer

El segundo es el control de tiempo basado en ventanas de Windows y es una actualizacion del que existe desde las primeras versiones de Visual Basic.

Es una versión idéntica a la de los controles timer de WIN 32 y esta pensada solo para trabajar con Windows.Forms, y se encuentra en la biblioteca de clases llamada System.Windows.Forms.Timer.-

Este timer (Windows Timer) esta diseñado para ambientes de hilo unico (single threaded) que es el unico en el que puede trabajar Visual Basic 6.0 y anteriores.-



Thread Timers


EL tercero es un timer para trabajo con hilos. Es el que utilizaremos en nuestros ejemplos y utiliza metodos de llamadas a funciones como parametros (callbacks).-
Este timer no tiene un control accesible como componente C# o de un Form y solo puede especificarse programaticamente.-
Las versiones del timer para trabajos con hilos, utilizaran las utilidades que figuran en el namespace System en System.Threading.Timer.-
El siguiente ejemplo al que llamaremos Timers_1 nos muestra algunos aspectos interesantes del uso de los timers de la biblioteca mencionada.-
El main instancia un objeto de la clase que crea los timers:

PruebaTimer test = new PruebaTimer();

Envia un mensaje por la consola:

Console.WriteLine("Timer iniciado por 6 segundos.");

y luego permite la ejecucion de los threads levantados en el constructor de la clase PruebaTimer
 
using System.Threading;

namespace Timers_1


{
  public class PruebaTimer
  {


     public ManualResetEvent timerevent;
     public PruebaTimer()
    {
            timerevent = new ManualResetEvent(false);
            //El primer timer trabaja CADA 5 segundos y en cada lapso
           //invoca al metodo Metodotimer1
         
        Timer timer = new Timer(new TimerCallback(this.MetodoTimer1),
                         null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5));






//El segundo timer trabaja CADA 1 segundo y en cada lapso
//invoca al metodo Metodotimer2


Timer Timer2 = new Timer(new TimerCallback(this.MetodoTimer2),
                                               null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1));






   }






   public void MetodoTimer1(object state)
 {


        Console.WriteLine("\nEn el 6to segundo,el Timer invoca este metodo.");


          timerevent.Set();


  }






public void MetodoTimer2(object state)
{


     Console.Write(".");


}


public static void Main()
{


   PruebaTimer test = new PruebaTimer();


   Console.WriteLine("Timer iniciado por 6 segundos.");


   //Bloquea el thread principal hasta que reciba la señal
  //del thread hijo (en este caso el creado con PruebaTimer)


    test.timerevent.WaitOne();


    Console.WriteLine("Mientras no Pulse ENTER para terminar, el timer seguira ejecutandose");


    Console.ReadLine();


}


}


}





Observemos lo que nos informa la ejecucion del programa en su salida por la consola:


A) Primero se ejecuta la sentencia del main, pese a haber instanciado previsamente un objeto de la clase Timer. Esto sucedera hasta que el thread del main quede bloqueado y eso permita a los otros threads seguir ejecutandose.-
El bloqueo del thread del main se produce al ser invocado el metodo WaitOne del atributo publico timerEvent de la clase PruebaTimer.-



test.timerevent.WaitOne();





B) Luego se ejecutan los metodos llamados por los timers.-

Se observa que lo hacen en estricto orden de creacion .Es decir primero el metodo llamado el primer timer instanciado y luego el metodo del segundo timer instanciado.-



Timer timer = new Timer(new TimerCallback(this.MetodoTimer1),


…..



Timer Timer2 = new Timer(new TimerCallback(this.MetodoTimer2),

….


C) Finalmente observemos que en la instanciacion del timer, se coloca no solo el metodo que sera invocado en cada lapso, sino ademas cada cuanto tiempo, dicho lapso se ejecuta.-

//El primer timer trabaja CADA 5 segundos y en cada lapso
//invoca al metodo Metodotimer1


Timer timer = new Timer(new TimerCallback(this.MetodoTimer1),
                                                    null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5));



D) El modo en el que los threads se informan del inicio y fin de su actrividad, permite una sincronizacion simple.

En el ejemplo elegimos usar la clase ManualResetEvent que:

1- Nos permite a traves de WaitOne detener un thread bloquendo su ejecucion y permitiendo que otros threads encolados puedan ejecutarse

2- Y a traves de Set nos permiter levantar una señal indicando la finalizacion de la tarea de un thread para que el que estaba bloqueado continue.-

E) La ejecucion nos muestra que el timer, una vez que está levantado, se ejecuta con periodicidad, pese a que el thread principal es el que tiene nuevamente el control.-

Se puede observar que los metodos llamados en cada lapso (eventos) son ejecutados hasta que se pulsa la tecla ENTER.-