domingo, 19 de julio de 2015

Receta Multithreading en C# No. 3-1: Invocación de un Delegado en un Pool de Threads

Índice

0. Introducción
1. Problema
2. Solución
3. Discusión de la Solución
3.1 Delegados
3.2 Expresiones lambda
3.3 Asynchronous Programming Model (APM)
3.4 Task Parallel Library (TPL)
4. Práctica: Código C#
5. Conclusiones
6. Glosario
7. Literatura & Enlaces

0. Introducción

Tenemos la gran oportunidad de empezar una nueva serie de recetas multithreading en C# en las que aprenderemos técnicas para el trabajo de recursos compartidos a través de múltiples threads: uso de un pool de threads. Aquí veremos temas como:
  • Invocación de un delegado desde un pool de threads
  • Exposición de un operación asincrónica en un pool de threads
  • Grado de paralelismo en un pool de threads
  • Uso de temporizadores y manejadores de espera, 
  • Uso del componente BackgroundWorker.
Claro que en primer lugar veremos efectivamente en qué consiste un pool de threads y su ventaja frente a la instanciación individual de threads (la cual es una operación costosa en espacio de memoria y tiempo de procesador). Para ello .NET Framework cuanta con una implementación particular y consistente de pool de threads y es la que estudiáremos en estas recetas. Estudio que se enfocará en en la clase System.Threading.ThreadPool.

En esta primera receta veremos cómo invocar un delegado en un pool de threads. Esto nos permitirá ejecutar de forma asincrónica una operación asociada a un delegado en un thread localizado en un pool de threads. También hablaremos acerca del primer patrón de programación asincrónica disponible en .NET Framework: Asynchronous Programming Model (APM).

1. Problema

Invocar de forma asincrónica un proceso.

2. Solución

A través de los delegados podemos invocar de modo asincrónico un proceso.

3. Discusión de la Solución

3.1 Delegados

El primer prerrequisito de esta discusión es conocer cómo declarar y usar delegados; sin embargo, omito su discusión en esta sección. Para saber más acerca de estas construcciones recomiendo la lectura de los siguientes artículos C#:

3.3 Asynchronous Programming Model (APM)

El patrón Asynchronous Programming Model [12] es un modelo creado por .NET Framework para la ejecución de operaciones asincrónicas, es decir, regiones de código que han de ejecutarse en threads independientes. Los métodos que implementan este patrón o modelo siguen esta nomenclatura:
  • BeginNombreMetodo, y 
  • EndNombreMetodo
Ejemplo de uso:

En la línea 11 creamos un objeto Stream para hacer referenciar a un instancia concreta de esta jerarquía de elementos de programa para la manipulación de archivos. En la línea 14 definimos el tamaño del búfer. Definimos, además, la propiedad TamanioBufer (líneas 17-27) para obtener y establecer el tamaño del búfer según sea necesario en el tratamiento de un archivo en particular. Sobre la línea 30 se define el arreglo de elementos byte para contener los datos leídos desde el flujo.


Continuando, en el constructor de ProcesadorLecturaAsincronica(string) (líneas 32-40) creamos efectivamente el búfer con la capacidad definida en tamanioBufer, y creamos una instancia FileStream con las siguientes características:
  • Referenciamos el nombre de archivo pasado como argumento: nombreArchivo.
  • El archivo se abrirá, se accederá en modo lectura, y el modo de compartición entre procesos será de lectura.
  • El tamaño del búfer para las lecturas asincrónicas.
  • Lectura asincrónica: true.
Con el método IniciarLectura se inicia la lectura asincrónica del archivo pasado como argumento en el constructor:
  • Línea 45: Con el método BeginRead empezamos la lectura asincrónica del archivo referenciado por inputStream.
Una vez finalizada la lectura del primer bloque, el callback OnLecturaFinalizada es disparado y se comprueba si aún restan bytes por leer:

int bytesLeidos = inputStream.EndRead(asyncResult);

De ser así, se invoca nuevamente nuevamente el método BeginRead:

inputStream.BeginRead(bufer, 0, bufer.Length, OnLecturaFinalizada, null);

Cada vez que se haga una llamada a BeginRead, se simula un tiempo de rutina larga en la línea 59:

Thread.Sleep(TimeSpan.FromMilliseconds(20));

Ahora, creemos el código cliente para probar esta clase:

Archivo C# DemoLecturaAsincronica.cs [Enlace alternativo][Enlace alternativo]:

En las líneas 16-19 instanciamos un objeto FileStream para la creación de un archivo de texto que nos servirá de prueba para la lectura asincrónica.

Con 

ProcesadorLecturaAsincronica plASync = new ProcesadorLecturaAsincronica("ArchivoPrueba.txt");

en la línea 23 creamos la instancia plAsync:ProcesarLecturaAsincrónica para procesar a modo asincrónico el archivo de texto recién creado: ArchivoPrueba.txt. E inmediatamente se inicia la lectura de este archivo con la invocación de IniciarLectura (línea 24).

Con el ciclo while (líneas 29-36) simulamos operaciones que se ejecutarán en `Main` de modo paralelo con la lectura asincrónica de ArchivoPrueba.txt.


Compilación:

  1. csc /t:exe DemoLecturaAsincronica.cs ProcesadorLecturaAsincronica.cs 


Ejecución assembly:


  1. .\DemoLecturaAsincronica.exe

> Prueba de ejecución:
Ejecución assembly DemoLecturaAsincronica.exe
Animación 1. Ejecución assembly DemoLecturaAsincronica.exe.

3.4 Task Parallel Library (TPL)

En [1] advierten: 
"...This pattern [APM] is still being used in various .NET class library APIs, but in modern programming, it is preferable to use Task Parallel Library (TPL) for organizing an asynchronous API."
Luego para el programador es importante reconocer que existe esta nueva API para la simplificación del proceso de añadir concurrencia y paralelismo a sus aplicaciones. Uno de los puntos a favor de esta librería es:
"The TPL scales the degree of concurrency dynamically to most efficiently use all the processors that are available."
Y entre las características sobresalientes tenemos [13]
  • división de tareas, 
  • programación de threads en un objeto ThreadPool
  • soporte para cancelación de tareas, 
  • gestión de estado.
(Nota: Nos adentraremos en el estudio de esta librería en futuros artículos y recetas.)

4. Práctica: Código C#

Haremos una demostración utilizando dos enfoques de ejecución de threads: creación de un thread e invocación de un delegado asociado a un proceso y su ejecución asincrónica.

Archivo C# InvocacionDelegadoEnPoolThreads.cs [Enlace alternativo][Enlace alternativo]:

En la línea 11 declaramos el delegado EjecutarEnPoolThread(out int). En el método Main (líneas 13-44) ocurren las siguientes operaciones:
  • Línea 17: Creación de variable entera para contener el ID de un thread.
  • Línea 20: Creación de un objeto Thread al que pasamos como argumento una expresión lambda que corresponde asocia el método Proceso.
  • Línea 22: Esperamos a que el thread recién creado finalice la invocación para continuar la ejecución de Main.
  • Línea 24: Muestra el ID asociado al thread anterior.
  • Línea 27: Creación de instancia de delegado EjecutarEnPool: delegadoEnPool.
  • Línea 29: Invocación de BeginInvokeEste método recibe como argumentos:
    • un objeto por referencia. Objeto que es útil para obtener información durante la ejecución asincrónica del método Proceso.
    • un callback útil para que una vez finalizada la operación asincrónica (Proceso) se ejecute, y
    • un estado. Útil para distinguir una llamada asincrónica de otra.
  • Línea 33: Invocación de EndInvoke. Obtiene el resultado de la ejecución de Proceso. Nota: Es importante incluir esta llamada debido a que es posible que se generen excepciones no controlables en el thread de origen de la invoación (en este caso, Main).
  • Línea 38: Mostramos el mensaje (string) devuelto por Proceso de forma asincrónica.
Compilación:
  1. csc /target:exe InvocacionDelegadoEnPoolThreads.cs
Ejecución assembly:
  1. .\InvocacionDelegadoEnPoolThreads.exe
> Prueba de ejecución: http://ideone.com/3iB93B

> Prueba de ejecución: 
Ejecución assembly InvocacionDelegadoEnPoolThreads.exe
Animación 2. Ejecución assembly InvocacionDelegadoEnPoolThreads.exe.

5. Conclusiones

Estudiamos la forma de invocar un delegado en un pool de threads. Hemos visto que resulta sencillo hacerlo a través de este modelo (i.e., Asynchronous Programming Model); sin embargo, como vimos en la sección 3.4 en .NET Framework existe un nuevo enfoque llamado Task Parallel Library que es mucho más sencillo y que lo verificaremos en futuras recetas o artículos C#. Nos quedó claro, además, que dominar el concepto práctico de delegados nos facilita enormemente comprender cómo ejecutar procesos de asincrónicamente.

6. Glosario

  • API
  • Concurrencia
  • Delegado
  • Modelo
  • Paralelismo
  • Patrón
  • Proceso
  • Proceso asincrónico

7. Literatura & Enlaces

[1]: Multithreading in C# 5.0 Cookbook by Eugene Agafonov. Copyright 2013 Eugene Agafonov, 978-1-84969-764-4.
[2]: Delegados en C# - Parte 1: Introducción - http://ortizol.blogspot.com/2014/05/Delegados-en-csharp-parte-1-introduccion.html
[3]: Delegados en C# - Parte 2: Métodos Anónimos - http://ortizol.blogspot.com/2014/05/delegados-en-csharp-parte-2-metodos-anonimos.html
[4]: Delegados en C# - Parte 3: Eventos - http://ortizol.blogspot.com/2014/05/delegados-en-csharp-parte-3-eventos.html
[5]: Delegados en C# - Parte 4: Compatibilidad de Delegados - http://ortizol.blogspot.com/2014/05/delegados-en-csharp-parte-4-compatibilidad-de-delegados.html
[6]: Delegados en C# - Parte 5: Delegados Func y Action de .NET Framework - http://ortizol.blogspot.com/2014/05/delegados-en-csharp--parte-5-delegados-func-y-action-de-dotnet-framework.html
[7]: Expresiones Lambda en C# - Parte 1: Introducción a las Expresiones Lambda - http://ortizol.blogspot.com/2014/06/expresiones-lambda-en-csharp-parte-1-introduccion-a-las-expresiones-lambda.html
[8]: Expresiones Lambda en C# - Parte 2: Expresiones Lambda Delegados Func y Action de .NET Framework - http://ortizol.blogspot.com/2014/06/expresiones-lambda-en-csharp-parte-2-expresiones-lambda-delegados-func-y-action-de-dotnet-framework.html
[9]: Expresiones Lambda en C# - Parte 3: Expresiones Lambda y LINQ - http://ortizol.blogspot.com/2014/06/expresiones-lambda-en-csharp-parte-3-expresiones-lambda-y-linq.html
[10]: Expresiones Lambda en C# - Parte 4: Expresiones Lambda y Asincronismo - http://ortizol.blogspot.com/2014/06/expresiones-lambda-en-csharp-parte-4-expresiones-lambda-y-asincronismo.html
[11]: Expresiones Lambda en C# - Parte 5: Ejemplos de Expresiones Lambda - http://ortizol.blogspot.com/2014/06/expresiones-lambda-en-csharp-parte-5-ejemplos-de-expresiones-lambda.html
[12]: Asynchronous Programming Model (APM) - https://msdn.microsoft.com/en-us/library/ms228963(v=vs.110).aspx
[13]:Task Parallel Library (TPL) - https://msdn.microsoft.com/en-us/library/dd460717(v=vs.110).aspx


V

No hay comentarios:

Publicar un comentario

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