Creación de hilos con parámetros en C#
January 16th, 2006Muchos lenguajes de programación permiten la creación de hilos o threads en un programa. De forma resumida, los hilos son un mecanismo mediante el cual podemos devidir una aplicación en diferentes partes que se pueden ejecutar de forma paralela, existiendo mecanismos por los que pueden compartir información.
C# ofrece un mecanismo muy sencillo de implementar hilos, basado en la utilización de la clase Thread
. El constructor de esta clase recibe como parámetro el método o función que hay que ejecutar en paralelo. Este parámetro se indica mediante la utilización de un delegado, que es el mecanismo que, entre otras cosas, se utiliza en .NET para utilizar punteros a funciones de forma segura. La firma del delegado no incluye ningún parámetro, por lo que únicamente es posible crear hilos de forma directa sobre métodos y funciones que no requieran parámetros de entrada ni de salida. En los siguientes ejemplos muestro un caso sencillo de creación de un hilo y otro en el que explico una forma de poder crear un hilo con entrada y salida de parámetros.
En el siguiente ejemplo se dispone de una clase con dos métodos que muestran mensajes por pantalla. El objetivo es crear dos hilos, uno para cada uno de los métodos y ejecutarlos de forma paralela, de forma que podamos ver como resultado cómo se van intercalando los mensajes escritos por cada método.
using System;
using System.IO;
using System.Threading;
public class Mensajes{
public void Mostrar1()
{
for(int i=0;i<10;i++){
Console.WriteLine("Escribiendo desde ==> 1″);
Thread.Sleep(1000);
}
}
public void Mostrar2()
{
for(int i=0;i<10;i++){
Console.WriteLine("Escribiendo desde ==> 2″);
Thread.Sleep(1000);
}
}
}
public class Ejemplo{
public static void Main()
{
Mensajes msg = new Mensajes();
Thread th1 = new Thread(new ThreadStart(msg.Mostrar1));
Thread th2 = new Thread(new ThreadStart(msg.Mostrar2));
th1.Start();
th2.Start();
th1.Join();
th2.Join();
}
}
La creación de cada hilo se realiza mediante las líneas Thread th1 = new Thread(new ThreadStart(msg.Mostrar1));. Esta línea indica que se crea una instancia de la clase Thread, con nombre th1, a partir de un delegado de la clase ThreadStart, que apunta al método Mostrar1 del objeto msg creado anteriormente.
Una vez creados los dos hilos hay que activarlos, para lo que se llama al método Start de cada uno de ellos. Tras este punto cada hilo se ejecuta en paralelo entre si, y con el programa principal, por lo que utilizamos el método Join de ambos hilos para esperar a que terminen los hilos antes de finalizar el programa.
El delegado ThreadStart no acepta parámetros de entrada ni de salida, por lo que si queremos crear un hilo sobre un método que los necesite, hay que utilizar algún mecanismo auxiliar. Una posible forma de conseguir esto es crear una nueva clase con los parámetros necesarios en la entrada y con un nuevo método sin parámetros que llame al método que queremos hacer paralelo, enviándole estos parámetros. A partir de aquí tendríamos que crear una instancia de dicha clase con los parámetros que queremos enviar al método original, y hacer que el hilo se ejecutase sobre el nuevo método de la clase. En el caso de que quisiéramos obtener el resultado de la ejecución, deberíamos crear una función que acepte como parámetro de entrada el tipo del valor devuelto por el método original, y hacer que la nueva clase creada disponga también de un delegado que indique la función a la que llamar tras la ejecución.
Como esto puede parecer un poco lioso, vamos a ver otro ejemplo. En esta ocasión disponemos de una clase de funciones matemáticas y queremos llamar de forma paralela a una de ellas. Este método acepta un valor entero en la entrada y devuelve otro entero.
using System;
using System.Threading;
using System.IO;
public class EjemploMates{
public static int CalculoComplejo(int n)
{
// sumo uno y espero 5 segundos
n = n+1;
Thread.Sleep(5000);
return n;
}
}
public class HiloParaMates{
protected int n;
protected MatesCallback callback = null;
public HiloParaMates(int n, MatesCallback callback){
this.n = n;
this.callback = callback;
}
public void CalculoComplejo()
{
int result = EjemploMates.CalculoComplejo(n);
if(callback != null)
callback(result);
}
}
// creo un delegado con la firma necesaria para capturar
// el valor devuelto por el método CalculoComplejo
public delegate void MatesCallback(int n);
public class Ejemplo{
public static void Main()
{
HiloParaMates hpm = new HiloParaMates(1000, new MatesCallback(ResultCallback));
Thread th = new Thread(new ThreadStart(hpm.CalculoComplejo));
th.Start();
th.Join();
}
public static void ResultCallback(int n)
{
Console.WriteLine("Resultado de la operación: "+n);
}
}
En el anterior código la clase HiloParaMates
es la que nos permite encapsular la llamada al método EjemploMates.Calcular
. Este método requiere un parámetro de tipo entero, por lo que la clase requiere este parámetro en su constructor. Además se requiere en el constructor otro parámetro más, un delegado MatesCallback, que acepta un entero en la entrada. La idea es que tras realizar el cálculo se llame al método que se indique proporcionándole el resultado.
Para hacer funcionar todo esto, en Main se crea una instancia de la clase HiloParaMates indicándole que queremos utilizar el valor numérico 1000 y que se llame al método (estático) ResultCallback cuando se obtenga el resultado. Para crear el hilo es suficiente con indicar que se quiere hacer sobre el método CalculoComplejo de la instancia hpm.
Conclusiones
Por supuesto todo lo anterior son únicamente ejemplos muy sencillos, ya que la programación de un sistema multihilo suele ser bastante compleja debido al indeterminismo implícito en la ejecución de múltiples hilos de ejecución, que normalmente comparten recursos o dependen unos de otros. En cualquier caso sí que nos permite apreciar la facilidad con la que se pueden crear estas estructuras en C#, olvidándonos de complejas sintaxis y librerías, y centrándonos únicamente en los requisitos de nuestro sistema.
delicious
menéame
fresqui

January 16th, 2006 a las 2:26 pm
Todo esto es muy bonito, pero muy complejo, por que no creas una nueva clase derivada de Thread, y sobreescribiendo el método Run.
January 16th, 2006 a las 2:49 pm
Teniendo en cuenta que Thread es sealed, la opción de crear una clase derivada no parece muy viable.
January 16th, 2006 a las 11:25 pm
Es cierto, pensaba que funcionaba como en Java o en Delphi, donde si se pude derivar de la clase de thread, e incluso en Java esta mejor ya que solo hay que implementar una interfaz.
En Net.2 se pude hacer un hilo al que se le pasa un parametro del tipo Object.
En vez de hacer todo eso se puede iniciar con una función que recibe un parametro del tipo Object, lo curioso es que para pasar un parametro al hilo en Net 1 tengas que hacer todas esas cosas cuando desde el API de windows a la función que crea hilos siempre se pude pasar un parámetro.
March 1st, 2006 a las 3:21 pm
Yo también pensé que funcionaba como en el delphi, y alguien me habló que en java era mucho más fácil según Ricardo Zuares. Lo más complicado de esto es el delegado que para cuand es un solo hilo no tiene muchas deficultades pero cuando se trata de varios se puede complicar la cosa…
March 25th, 2006 a las 3:06 pm
Es verdad que no es muy sencilo pero ya de una vez implementado por otro es más fácil pero mi duda no es al implementar el hilo sino como y cuando la aplicación principal puede poner a trabajar o correr al hilo implementado, es decir ya yo hice mi aplicación (que correra como un hilo) pero ¿cómo le digo al hilo que debe empezar a ejecutarse cuando yo lo necesito?
March 25th, 2006 a las 8:49 pm
El hilo comienza a ejecutarse cuando llamas al método “Start”, hasta entonces únicamente está creado.
May 9th, 2006 a las 9:29 pm
en java tambien se puede hace de esta forma,
la opcion de heredar de la clase thread es valida en java y aparentemente no en c#, pero esta opcion tiene limitaciones, ya que como tenes que herdar y no se permite la herencia multiple, no podes hacer un thread que herede de otra clase.
En cambio en c# no hace falta ni heredar una interfaz, igualmente hay que respetar el prototipo del delegate, que es mas o menos lo mismo
November 8th, 2006 a las 12:29 am
podrias poner un ejemplo usando windowsforms; ya q en consola si me corre pero en la interfaz grafica no.
Gracias
August 16th, 2007 a las 4:50 pm
Hola. En el Framework 2.0 un hilo puede inicializarse con un delegado del tipo ParameterizedThreadStart. Con esto podemos usar hilos para métodos que reciben valores (pero que no regresan valores… es decir que !!NO SIRVE!! pa el ejemplo que usted presenta). De todos modos hago un medio ejemplo pa aportar algo…..
public static void Main()
{
Thread th = new Thread(new
ThreadStart(EjemploMates.CalculoComplejo));
th.Start(1000); //
August 17th, 2007 a las 9:56 am
Hola Daniel, parece que se te ha cortado el código. De todas formas, tal como dices en el Framework 2.0 existe la opción de crear hilos mediante ParametrizedThreadStart y pasar parámetros al hacer el start. De hecho, esta creación se reconoce de forma automática y no hace falta utilizar la clase concreta, es suficiente con Thread. En el ejemplo que pongo podría haber sido de la siguiente forma (tal como tú dices):
Thread th = new Thread(new ThreadStart(EjemploMates.CalculoComplejo));
th.Start(1000)
Sin embargo, para la devolución de parámetros desde el hilo hay que seguir haciendo lo mismo que en el ejemplo, es decir, pasar al método un delegado como parámetro, para que lo invoque cuando termine el proceso. Para ello habría que modificar EjemploMates.CalculoComplejo para que aceptase un parámetro de tipo delegado, y hacer que al terminar el proceso lo invocase:
public class EjemploMates{
public static int CalculoComplejo(int n, MatesCallback callback)
{
// sumo uno y espero 5 segundos
n = n+1;
Thread.Sleep(5000);
if(callback != null)
callback(n);
return n;
}
}
Con esto la creación del hilo quedaría:
Thread th = new Thread(new ThreadStart(EjemploMates.CalculoComplejo));
th.Start(1000, new MatesCallback(ResultCallback));
Espero que sirva
October 6th, 2007 a las 8:10 pm
HOla,
si se hace:
t1.Join();
t2.Join();
primero esperaría a que acabara el primer hilo (t1.Join();) y luego cuando haya acabado de ejecutarse el primer hilo, se ejecutaría la instrucción t2.Join(); para esperar que acabara el segundo hilo.
Es esto correcto ???
Saludos.
October 8th, 2007 a las 3:11 pm
Sip, así es. De todas formas ten en cuenta que el método Join tiene otras sobrecargas que pueden utilizarse [http://msdn2.microsoft.com/en-us/library/system.threading.thread.join.aspx].
En el ejemplo descrito el programa no terminaría nunca si alguno de los hilos no terminara. Para remediar esta situación se podrían utilizar llamadas a Join con un determinado tiempo para obtener de nuevo el control tras ese lapso de tiempo, obteniendo si el hilo ha terminado o no, y poder actuar como proceda en esa situación, terminar el hilo o dando otro lapso de tiempo, por ejemplo.
October 28th, 2007 a las 1:43 am
Hola,
Tambien me gustaria ver un ejemplo de esto, pero usando Windows.Forms…
Si en mi programa principal tengo un string (string msg) y un TextBox (Textbox Console).
Podria desde un hilo obtener el string msg y luego escribir en Console el valor de este string??
Por favor si alguien me puede ayudar, lo agradecería demasiado!
Saludos.
October 29th, 2007 a las 10:26 am
Hola tavo, la respuesta es que sí, aunque al querer modificar un control del interfaz (el TextBox), tendrías que hacerlo de una forma especial, ya que esto sólo puede hacerse desde el hilo principal de la aplicación, en caso contrario puedes obtener efectos inesperados.
En .Net 2.0 podrías hacerlo de forma sencilla por ejemplo con:
textbox.Invoke((MethodInvoker) delegate {
textbox.Text = msg;
});
Si quieres tenerlo estructurado en métodos, o tienes versiones anteriores, tendrías que crear un delegado y utilizarlo con invoke.
Tienes un buen ejemplo en:
http://www.yoda.arachsys.com/csharp/threads/winforms.shtml
November 24th, 2007 a las 12:15 am
Hola Patxi.
Muchas gracias por la respuesta. Habia encontrado la solución (similar) justo antes, lamento no haber revisado tu respuesta antes.
De todas maneras, muchas gracias. Saludos desde Chile!
April 21st, 2008 a las 8:02 am
y como declaro el tiempo el en .join(?)