jueves, 24 de julio de 2014

Receta Multithreading en C# No. 1-8: Cómo Pasar Argumentos a un Thread

Índice

0. Introducción
1. Problema
2. Solución
3. Discusión de la Solución
3.1 Método sobrecargado Start(Object)
3.2 Delegado ParameterizedThreadStart
3.3 Ejemplo de uso de Start(Object) y ParameterizedThreadStart
4. Práctica: Código C#
5. Artefactos
6. Conclusiones
7. Glosario
8. Literatura & Enlaces

0. Introducción

En recetas multithreading anteriores hemos visto cómo ejecutar un thread utilizando la versión sobrecargada del método Start (Thread) con cero parámetros; sin embargo, existe la versión de este método que recibe un argumento de tipo Object que corresponde con la información que le podemos pasar al método de forma indirecta a través del delegado de tipo ParameterizedThreadStart. En esta receta vamos a aprender a utilizar este delegado y el método Start(object).

1. Problema

Necesitamos pasar información al método, el cual será ejecutado por un thread. La información deberá ser usada por el método en cuestión.

2. Solución

La clase Thread cuenta con una versión sobrecargada del método Start que permite pasar un argumento de tipo Object con la información a ser usada por el método ejecutado por un thread. Además, el constructor de Thread debe encapsular un método que cumpla con la firma del delegado ParameterizedThreadStart.

3. Discusión de la Solución

En primera instancia, exploremos el método Start(Object), y luego el delegado ParameterizedThreadStart.

3.1 Método sobrecargado Start(Object)


El método Start cuenta con las siguientes firmas:
Versiones sobrecargadas del método Start
Figura 1. Versiones sobrecargadas del método Start.
Nos interesa en esta receta trabajar con la segunda versión que admite como parámetro un instancia de Object: Start(Object) [3].

Firma del método:
public void Start(Object parameter)

Descripción puntual:
  • Parámetros:
    • Object parameter: Contenedor de la información usada por el método que ejecutará un thread.
Continuando, este método en conjunción con el delegado ParameterizedThreadStart, facilitan pasar datos al método ejecutado por un thread. Sin embargo en [3] nos advierten que:
«...but the technique is not type safe because any object can be passed to this overload...»
Para solventar este problema de seguridad de tipos (cfr. Seguridad de Tipos en C#), lo que podemos hacer es crear una clase utilitaria que encapsule los campos de instancia asociados al método ejecutado por el thread. (Hablaremos de este tema en futuras recetas o artículos, sin embargo, pueden consultar el siguiente artículo para saber más acerca de esta solución: [5].)

3.2 Delegado ParameterizedThreadStart

El delegado ParameterizedThreadStart [2] permite ejecutar un método sobre un thread. A diferencia de ThreadStartParameterizedThreadStart permite pasar datos al método que se ha de ejecutar en el thread. Esta es su firma:

public delegate void ParameterizedThreadStart(Object obj)

Descripción puntual:
  • Parámetros:
    • Object obj: Objeto contenedor de los datos para el método encapsulado por el delegado ParameterizedThreadStart.
Para poder iniciar la ejecución del método encapsulado por este delegado, es necesario que la instancia de Thread invoque al método Start [6].

3.3. Ejemplo de uso de Start(Object) y ParameterizedThreadStart

El siguiente ejemplo de uso demuestra cómo utilizar el delegado ParameterizedThreadStart junto con el método Start(Object).

Archivo C# UsoDelegadoParameterizedThreadStart.cs [enlace alternativo]:

La clase UsoDelegadoParameterizedThreadStart (líneas 6-48) contiene los métodos:
  • Tarea1: (líneas 36-40) este método está declarado estático y contiene un parámetro de tipo object.
  • Tarea2: (líneas 43-47) este es un método de instancia. Igualmente, recibe un parámetro de tipo object.
En el código cliente (líneas 8-33) se crea una nueva instancia (línea 14) de la clase Thread. A su constructor le pasamos una instancia del delegado ParameterizedThreadStart, la cual encapsula el método Tarea1.

Más adelante, en la línea 21, invocamos al método Start al que le pasamos como argumento una literal entera. Este valor será usado como argumento por el método Tarea1.

Para probar el método Tarea2, creamos una instancia (línea 24de la clase UsoDelegadoParameterizedThreadStart. Esta instancia la usamos en la línea 28 para encapsular el método Tarea2 con el delegado ParameterizedThreadStart de forma implícita. Invocamos al método Start en la línea 32. Una vez invocamos este método el estado (cfr. Obtener el Estado de un Thread) del thread pasa de Unstarted a Starting.

Compilación:

  1. csc /target:exe UsoDelegadoParameterizedThreadStart.cs

Ejecución assembly:

  1. .\UsoDelegadoParameterizedThreadStart.exe

> Prueba de ejecución (local):
Ejecución assembly UsoDelegadoParameterizedThreadStart.exe
Figura 2. Ejecución assembly UsoDelegadoParameterizedThreadStart.exe.

4. Práctica: Código C#

Este ejemplo va a resultar muy interesante porque demostraremos dos maneras adicionales de pasar información a un thread:
  1. Clase utilitaria con campos de instancia.
  2. Uso de expresiones lambda en el constructor de la clase Thread.
En las líneas 8-30 se declara la clase UtilitarioThread con los siguientes miembros:
  • Línea 10: Número de iteraciones para el ciclo for de las líneas 21-28.
  • Líneas 12-15: Constructor para inicializar el campo que representa el número de iteraciones.
  • Líneas 19-29: Método ContarNumeros para contar desde 1 hasta el número de iteraciones (_iteraciones).
Por otro lado, la clase sealed ParametrosThread posee los siguientes miembros:
  • Líneas 37-40: Método compatible con la firma del delegado ParameterizedThreadStart.
  • Líneas 44-54Método ContarNumeros para contar desde 1 hasta el número de iteraciones.
  • Líneas 57-60: Imprime un número en la salida estándar.
En el método Main destaco las siguientes líneas:
  • Línea 76: Creación de una instancia de Thread con el uso de una instancia explícita del delegado ParameterizedThreadStart. Este delegado encapsula el método static Contar.
  • Línea 78: Pasamos como argumento la literal entera 8 para el thread que ejecutará el método Contar.
  • Línea 82: (Quizás una de las interesantes o llamativas) Especificamos una expresión lambda (cfr. Expresiones Lambda en C# - Parte 1: Introducción a las Expresiones Lambda) con la cual creamos un método, el cual al mismo tiempo invoca al método ContarNumeros con el argumento literal entero 12.
  • Línea 89: Creación de otra instancia de Thread a la que le especificamos una expresión lambda en su constructor. En la línea 91 creamos otro thread con las mismas características. Cuando ejecutemos esta sección de código, el número que mostrará en pantalla el método ImprimirNumero (líneas 57-60) será 13. Esto se debe a que la variable local numero (declarada en la línea 88) es parte de una clase generada por el compilador de C#, la cual será compartida por todos las demás expresiones lambda que hayan encapsulada la variable local numero. (A esto último se le conoce como closure.)
Compilación:


  1. csc /target:exe ParametrosThread.cs

Ejecución assembly:


  1. .\ParametrosThread.exe

> Prueba de ejecución (ideone.com).

> Prueba de ejecución (local):
Ejecución assembly ParametrosThread.exe
Figura 3. Ejecución assembly ParametrosThread.exe.

5. Artefactos

Enlaces de descarga de los artefactos producidos en el desarrollo de esta receta:

6. Conclusiones

Hemos aprendido a pasar argumentos a un thread a través del uso del método Thread.Start(Object) y el delegado ParameterizedThreadStart. Vimos en el ejemplo de la sección práctica dos maneras diferentes de realizar esta tarea: clase utilitaria, y uso de expresiones lambda sobre el constructor de la clase Thread. En la próxima receta conoceremos el uso de la construcción lock para la sincronización de múltiples threads que intentan acceder a un recurso común o compartido.

7. Glosario

  • Closure
  • Contenedor
  • Expresión lambda
  • Iteración
  • Multithreading
  • Recurso compartido
  • Thread

8. Literatura & Enlaces

[1]: Multithreading in C# 5.0 Cookbook by Eugene Agafonov. Copyright 2013 Eugene Agafonov, 978-1-84969-764-4.
[2]: ParameterizedThreadStart Delegate (System.Threading) - http://msdn.microsoft.com/en-us/library/system.threading.parameterizedthreadstart%28v=vs.110%29.aspx
[3]: Thread.Start Method (Object) (System.Threading) - http://msdn.microsoft.com/en-us/library/6x4c42hc%28v=vs.110%29.aspx
[4]: Seguridad de Tipos en C# | OrtizOL - Experiencias Construcción Software (xCSw) - http://ortizol.blogspot.com/2013/06/seguridad-de-tipos-en-c.html
[5]: Creating Threads and Passing Data at Start Time - http://msdn.microsoft.com/en-us/library/ts553s52%28v=vs.110%29.aspx


J

No hay comentarios:

Publicar un comentario

Envíe sus comentarios, dudas, sugerencias, críticas. Gracias.