Mostrando entradas con la etiqueta Chapter 5. Mostrar todas las entradas
Mostrando entradas con la etiqueta Chapter 5. Mostrar todas las entradas

domingo, 3 de julio de 2016

Receta Multithreading en C# No. 5-4: Ejecución en Paralelo de Tareas con await

Índice

1. Introducción
2. Palabras Clave
3. Problema
4. Solución
5. Discusión de la Solución
5.1 Combinación de tareas con Task.WhenAll
6. Práctica: Ejecución en Paralelo de Tareas
7. Conclusiones
8. Literatura & Enlaces

1. Introducción

Aquí se explica cómo el programador puede lograr la ejecución en paralelo de múltiples tareas usando la función asincrónica await. El ejemplo de la sección práctica demuestra que esto es posible a través de la ejecución de dos tareas, cada una con 3 y 5 segundos de retrasa. Al final quedará demostrado que el tiempo máximo de espera será 5 segundos.

2. Palabras Clave

  • await
  • Ejecución en paralelo
  • Función asincrónica
  • Tarea

3. Problema

Ejecutar en paralelo dos o más tareas.

4. Solución

A través del operador await el programador puede implementar código de ejecución en paralelo.

5. Discusión de la Solución

5.1 Combinación de tareas con Task.WhenAll

El método static Task.WhenAll() ("Task.WhenAll Method", 2016) crea una tarea compuesta; una vez que el conjunto de tareas integrales finalicen su ejecución, el flujo de control de ejecución pasa a la tarea compuesta y se continua con la lógica de implementación particular a esta tarea.

Este método cuenta hasta con 4 versiones static sobrecargadas; tal y como se muestra en la siguiente figura: 
Versiones sobrecargadas de Task.WhenAll
Figura 1. Versiones sobrecargadas de Task.WhenAll ("Task.WhenAll Method", 2016).

6. Práctica: Ejecución en Paralelo de Tareas

El siguiente ejemplo en C# demuestra la ejecución de dos tareas en paralelo a través del uso de la función asincrónica await.

El método ObtenerInfoAsync(string, int) (líneas 46-54) representa una operación de ejecución asincrónica. Estas son las operaciones que lleva a cabo: 
  • Línea 48: Simula la ejecución extendida.
  • Líneas 50-53: Retorna cadena de caracteres con la información del thread que ejecuta la tarea.
Por otra parte, con el método ProcesamientoAsincronico() (líneas 25-38) se preparan para ejecución dos tareas; cada una con duración de 3 y 5 segundos. Los resultados se almacenarán en el arreglo de objetos string resultados (línea 30). Cuando estas dos tareas hayan finalizado su ejecución, se muestran los resultados de los threads de ejecución en el ciclo foreach de las líneas 34-37.

En esta captura se presenta el flujo de ejecución de esta interesante receta: 
> Prueba de ejecución (online): http://ideone.com/6yNUeO

7. Conclusiones

El operador await permite al programador del lenguaje de programación C# ejecutar en paralelo dos o más tareas. Hasta ahora se ha visto que el modelo TPL resulta más intuitivo y poderoso que los modelos EAP y APM estudiados en series de recetas multithreading anteriores.

La próxima receta multithreading enseña al programador cómo manipular excepciones en operaciones asincrónicas.

8. Literatura & Enlaces

Agafonov, E. (2013). Multithreading in C# 5.0 Cookbook. United States: Packt Publishing.
Task.WhenAll Method (System.Threading.Tasks) (2016, julio 3). Recuperado desde: https://msdn.microsoft.com/en-us/library/system.threading.tasks.task.whenall(v=vs.110).aspx


V

domingo, 26 de julio de 2015

Receta C# No. 5-26: Escritura y Lectura Entre Procesos

Índice

0. Introducción
1. Problema
2. Solución
3. Discusión de la Solución
3.1 Named pipes (tubierías nombradas)
3.2 Clase NamedPipeServerStream
3.3 Clase NamedPipeClientStream
4. Práctica: Código C#
5. Conclusiones
6. Glosario
7. Literatura & Enlaces

0. Introducción

Alcanzamos la última receta de la serie de recetas C# dedicadas a Archivos, Directorios, y Entrada y Salida. En esta ocasión vamos a construir una aplicación basada en consola para permitir a múltiples procesos comunicarse entre sí y ejecutar operaciones de lectura y escritura en conjunto. Para ello hemos de explorar y conocer las clases NamedPipeServerStream y NamedPipeClientStream para crear un servidor de escucha de conexiones y clientes de conexión para interacción mutua.

1. Problema

Requerimos intercomunicar dos procesos para el envío y recepción de datos.

2. Solución

El namespace System.IO.Pipes cuenta con dos clases diseñadas para permitir que dos procesos se puedan intercomunicar e intercambiar datos. Estas clases siguen el mecanismo de intercomunicación named pipes (o tuberías nombradas).

3. Discusción de la Solución

3.1 Named pipes (tuberías nombradas)

De acuerdo con [2] un named pipe (o tubería nombrada) es uno de los varios mecanismos para la intercomunicación de procesos. A diferencia de los unamed pipes (los cuales se ejecutan de forma anónima y durante el ciclo de vida del proceso), los named pipes, además de contar con un identificador específico, pueden persistir más allá de la ejecución de un proceso y el sistema puede programarse para eliminarlos cuando sea requerido.
Named pipes
Figura 1. Diagrama conceptual de Named pipes.
Y de acuerdo con [3], un nombre alternativo para los named pipes es FIFO [5] debido a que los datos que son escritos primero son los primeros en ser leídos.

3.2 Clase NamedPipeServerStream

La clase NamedPipeServerStream [6] (namespace System.IO.Pipes) pertmite la comunicación entre procesos en los modos: 
  • unidireccional, y 
  • bidireccional
Esta clase actúa como el servidor de escucha de conexiones desde uno o más clientes. Y como explican en [6]: 
"Named pipes can be used for interprocess communication locally or over a network."
Lo que quiere decir es que podemos intercomunicar procesos inter-máquina (local) e intermáquina (red).


Nota importante de límites de número máximo de named pipes en algunos sistemas operacionales: 
"For Windows XP Professional and Windows 2000 Server, a maximum of 10 pipes can simultaneously connect over the network."
Ejemplo de uso: 

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

Sobre la línea 15 de la instrucción using creamos una instancia de NamedPipeServerStream para representar el servidor de escucha de conexiones desde clientes. Con la llamada a WiatForConnection (línea 21) esperamos a que un cliente se conecte para empezar el proceso de envío de datos.

En el bloque try (líneas 25-34) efectuamos las siguientes tareas: 
  • Línea 28: Creación de un objeto StreamWriter para escribir datos desde la consola y enviarlos a los clientes conectados al servidor.
  • Línea 32: Leemos la entrada del usuario y la envíamos a los clientes.
En caso de que se produzca un problema en la conexión con el cliente, se lanzará una excepción IOException reportando la causa del error (línea 38).

Compilación: 

  1. csc /target:exe UsoNamedPipeServerStream.cs

Ejecución assembly: 

  1. .\UsoNamedPipeServerStream.exe

3.2 Clase NamedPipeClientStream

Con la clase NamedPipeClientStream [7] (namespace System.IO.Pipes) creamos clientes para conexiones a named pipes. Podemos crear n instancias de objetos NamedPipeClientStream para establecer conexión con el servidor manejado por una instancia de NamedPipeServerStream.

Las conexiones se llevan a cabo a través del método Connect [8]. Este método también cuenta con una versión sobrecargada asincrónica [9] para soporte de lectura y escritura asincrónicas entre procesos.

Ejemplo de uso: 

En la línea 15 creamos un objeto NamedPipeClientStream que se conectará con un named pipe con el identificador servidor. Con el método Connect esperamos a que haya una conexión disponible. En el bloque using creamos un objeto StreamReader para recibir los mensajes desde el named pipe servidor. Mostramos los mensajes enviados desde el servidor en la línea 34.

Compilación: 


  1. csc /target:exe UsoNamedPipeClientStream.cs

Ejecución Assembly: 


  1. .\UsoNamedPipeClientStream.exe

> Prueba de ejecución: 
Ejecución assemblies UsoNamedPipeServerStream.exe y UsoNamedPipeClientStream.exe
Animación 1. Ejecución assemblies UsoNamedPipeServerStream.exe y UsoNamedPipeClientStream.exe.

4. Práctica: Código C#

Afiancemos nuestro conocimiento diseñando una nueva aplicación consola para crear un servidor y un cliente y facilitar entre ellos la intercomunicación para el envío y recepción de mensajes.

En el punto de entrada de la aplicación, Main (líneas 11-26), determinamos en qué modo debe iniciarse la aplicación consola: cliente o servidor.


En el método IniciarServidorPipe (líneas 28-60) ocurren las siguientes operaciones: 
  • Línea 31: Instanciación de un objeto NamedPipeServerStream para la creación de un named pipe identificado como servidor.
  • Línea 35: Invocación de WaitForConnection para esperar conexiones desde un cliente.
  • Líneas 45-50: Ciclo for para enviar hasta 10 datos  desde servidor hacia el cliente.
  • Líneas 53-56: Ciclo for para recibir hasta 10 datos desde el cliente.
  • Línea 59: Cierre del named pipe servidor.
Respecto al método IniciarClientePipe: 
  • Línea 65: Instanciación de un objeto NamedPipeClientStream para conexión con un named pipe identificado como servidor.
  • Línea 68: Intento de conexión con el servidor con el método Connect.
  • Líneas 76-76: Ciclo for para recibir hasta 10 datos desde el servidor.
  • Líneas 82-87: Ciclo for para enviar hasta 10 datos hacia el servidor.
  • Línea 90: Cierre del cliente con Close.
Compilcación: 

  1. csc /target:exe ServidorClienteNamedPipe.cs

Ejecución assembly: 

  1. .\ServidorClienteNamedPipe.exe

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

5. Conclusiones

Hemos aprendido que la intercomunicación entre procesos es muy sencilla. Esto gracias al uso de las clases NamedPipeServerStream y NamedPipeClienteStream, y al comprensión del mecanismo de intercomunicación de procesos pipe named (o tuberías nombradas).

Finalizamos esta serie de recetas C# de Archivos, Directorio, y Entrada y Salida. A partir de la siguiente serie nos centraremos en Procesamiento XML.

6. Glosario

  • Conexión
  • Dato
  • Intercomunicación
  • Name pipe
  • Proceso
  • Unamed pipe

7. Literatura & Enlaces

[1]: Visual C# 2010 Recipes by Allen Jones and Adam Freeman. Copyright 2010 Allen Jones and Adam Freeman, 978-1-4302-2525-6.
[2]: Named pipe - Wikipedia, the free encyclopedia - https://en.wikipedia.org/wiki/Named_pipe
[3]: Inter-process communication - Wikipedia, the free encyclopedia - https://en.wikipedia.org/wiki/Inter-process_communication
[4]: What is named pipe? - Definition from WhatIs.com - http://whatis.techtarget.com/definition/named-pipe
[5]: FIFO (computing and electronics) - Wikipedia, the free encyclopedia - https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)
[6]: NamedPipeServerStream Class (System.IO.Pipes) - https://msdn.microsoft.com/en-us/library/system.io.pipes.namedpipeserverstream(v=vs.110).aspx
[7]: NamedPipeClientStream Class (System.IO.Pipes) - https://msdn.microsoft.com/en-us/library/system.io.pipes.namedpipeclientstream(v=vs.110).aspx
[8]: NamedPipeClientStream.Connect Method (System.IO.Pipes) - https://msdn.microsoft.com/en-us/library/system.io.pipes.namedpipeclientstream.connect(v=vs.110).aspx
[9]: NamedPipeClientStream.ConnectAsync Method (System.IO.Pipes) - https://msdn.microsoft.com/en-us/library/system.io.pipes.namedpipeclientstream.connectasync(v=vs.110).aspx


V

sábado, 25 de julio de 2015

Receta C# No. 5-25: Procesamiento de un Archivo de Registro (Log)

Índice

0. Introducción
1. Problema
2. Solución
3. Discusión de la Solución
3.1 Método static File.ReadLines
3.2 Filtros con LINQ
4. Práctica: Código C#
5. Conclusiones
6. Glosario
7. Literatura & Enlaces

0. Introducción

En la receta C# anterior (5-24) aprendimos a escribir información de eventos (e.g., errores, excepciones) en un archivo de registros de eventos; la oportunidad de ahora consiste en procesar estos archivos con el uso del método static File.ReadLines y el uso de expresiones LINQ para obtener líneas específicas en el archivo.

1. Problema

Contamos con una serie de archivos de registros de eventos y queremos procesar su contenido.

2. Solución

En C# contamos con el método File.ReadLines [2] para leer el contenido de todas las líneas de un archivo de texto. Podemos procesar esas líneas de texto con consultas o filtros LINQ.

3. Discusión de la Solución

3.1 Método static File.ReadLines

El método sobrecargado static File.ReadLines [2] lee cada una de las líneas de un archivo. En la Figura 1 de muestran las versiones sobrecargadas de este método. 
Versiones sobrecargadas de File.ReadLines
Figura 1. Versiones sobrecargadas de File.ReadLines [2].

Vale mencionar, que a diferencia del método File.ReadAllLines [3], File.ReadLines permite trabajar de forma más eficiente con archivos grandes gracias a que la enumeración de la colección de cadenas se ejecuta primero antes de retornar la colección de objetos string [2]; esto no ocurre con File.ReadAllLines, dado que se debe esperar hasta que la colección de objetos string se retorna para hacer cualquier enumeración y/o aplicación de un filtro.


File.ReadLines usa el sistema de codificación UTF-8 para las cadenas de caracteres [2]. Por otra parte, podemos usar LINQ para filtrar las líneas contenidas en un archivo leído por File.ReadLines.

3.2 Filtros con LINQ

No nos extenderemos en esta sección de creación de filtros con el lenguaje de consulta integrado -LINQ-. Incluyo el siguiente enlace para conocer los detalles de la receta que explica el proceso de selección de elementos específicos de una colección -Receta No. 2-17 en C#: Selección de Elementos de un Arreglo o Colección-, además, del siguiente código de ejemplo extraído desde esa misma receta: 

En las líneas 9-28 declaramos la clase Fruta con un constructor, y dos propiedades (i.e., Nombre, y Color). La clase de prueba, FrutaLinq (líneas 30-73) tiene declarado el punto de entrada a la aplicación, Main: 
  • Línea 35: Creación de una instancia concreta de List con objetos Fruta.
  • Línea 49: Usamos la versión estándar de LINQ para seleccionar aquellas frutas con color distinto a rojo y que su nombre no empiece por el carácter 'p'.
  • Línea 61: Aquí utilizamos métodos de extensión (Métodos de Extensión en C#) de LINQ (i.e., Where, OrderBy, Select) con expresiones lambda para hacer la misma consulta anterior.
Compilación: 

  1. csc /target:exe FrutaLinq.cs

Ejecución assembly: 

  1. .\FrutaLinq.exe

> Prueba de ejecución: http://ideone.com/b2OgMj

> Prueba de ejecución: 
Ejecución assembly FrutaLinq.exe
Figura 2. Ejecución assembly FrutaLinq.exe.

4. Práctica: Código C#

En esta receta reutilizaremos el archivo log_completo.log ([enlace alternativo][enlace alternativo]) creado en la receta anterior para obtener aquellas entradas (líneas) que sólo incluyan al principio la palabra Error. Lo anterior usando LINQ.

Con la expresión 

IEnumerable<string> registros = File.ReadAllLines("log_completo.log");


(línea 18) leemos todas las líneas del archivo de registros de eventos. Notemos que aquí hemos definido a registros como un tipo IEnumerable para objetos string. Con el ciclo foreach (líneas 19-22) mostramos cada una de las líneas en la colección referenciada por registros.

Con 

IEnumerable<string> registrosError = File.ReadLines("log_completo.log").Where(e => e.StartsWith("Error"));

(línea 25) usamos LINQ y su método de extensión Where para crear la consulta que selecciona aquellos líneas del log que empiezan con los caracteres Error.


Compilación: 


  1. csc /target:exe SeleccionRegistros.cs


Ejecución assembly: 


  1. .\SeleccionRegistros.exe

> Prueba de ejecución: 
Ejecución assembly SeleccionRegistros.exe
Figura 3. Ejecución assembly SeleccionRegistros.exe.

5. Conclusiones

Hemos visto cómo es de sencillo seleccionar todos o algunos registros de un archivo de log utilizando LINQ (en especial para el segundo caso). Conocer LINQ ya se convierte en una necesidad explícita y urgente (seguramente en la proximidad ya empezaremos a redactar recetas LINQ).


La siguiente receta C# es la última de la serie de Archivos, Directorios, y Entrada y Salida: aprenderemos a enviar y recibir datos entre distintos procesos.

6. Glosario

  • Archivo
  • Consulta
  • Directorio
  • Filtro
  • LINQ
  • Proceso
  • Selección

7. Literatura & Enlaces

[1]: Visual C# 2010 Recipes by Allen Jones and Adam Freeman. Copyright 2010 Allen Jones and Adam Freeman, 978-1-4302-2525-6.
[2]: File.ReadLines Method (System.IO) - https://msdn.microsoft.com/en-us/library/system.io.file.readlines(v=vs.110).aspx
[3]: File.ReadAllLines Method (System.IO) - https://msdn.microsoft.com/en-us/library/system.io.file.readalllines(v=vs.110).aspx
[4]: Receta No. 2-17 en C#: Selección de Elementos de un Arreglo o Colección - http://ortizol.blogspot.com/2014/05/receta-no-2-17-en-c-seleccion-de.html
[5]: Métodos de Extensión en C# - http://ortizol.blogspot.com/2014/09/metodos-de-extension-en-csharp.html


V

Receta C# No. 5-24: Cómo Escribir Datos de Registro (Log) a un Archivo

Índice

0. Introducción
1. Problema
2. Solución
3. Discusión de la Solución
3.1 Método static File.WriteAllLines
4. Práctica: Código C#
5. Conclusiones
6. Glosario
7. Literatura & Enlaces

0. Introducción

En esta ocasión tendremos la oportunidad de aprender el proceso de escritura de registros de actividad, excepciones, errores, advertencias, y demás información relacionada de los eventos de una aplicación en un archivo de registro (o log, si prefieren el anglicismo) (aunque es posible escribir en otros medios como bases de datos, en una cola de mensajería, correo electrónico, sobre un evento WMI, a través de librerías especializadas como Logging Application de Enterprise Library). ¡Empecemos!

1. Problema

El diseño de una aplicación requiere escribir eventos de errores, excepciones, etc. en un registro de eventos en formato de texto plano.

2. Solución

Una solución práctica para este requerimiento es utilizar el método File.WriteAllLines (System.IO).

3. Discusión de la Solución

3.1 Método static File.WriteAllLines

Con el método static File.WriteAllLines [2] (System.IO) podemos escribir en un nuevo archivo una o más líneas de texto (registros, en este caso) contenidas en un arreglo u objeto IEnumerable [3], y una vez finalizada la escritura cerrar el archivo. Este método posee las siguientes versiones sobrecargadas: 
Versiones sobrecargadas de File.WriteAllLines
Figura 1. Versiones sobrecargadas de File.WriteAllLines [2].
Continuando, el sistema de codificación para la escritura de datos de estos métodos es UTF-8; sin embargo, es posible cambiarlo con las dos última versiones sobrecargadas que se muestran en la Figura 1 (i.e., [4, 5]).

Vale advertir que si el archivo destino para escritura ya existe en el sistema de archivos, éste es sobrescrito.

Creemos un ejemplo de uso con la versión sobrecargada WriteAllLines(string, string[]):

Ejemplo de uso: 

En la línea 20 declaramos un arreglo de elementos string para contener cada uno de los registros de evento. En las líneas 23 y 31 creamos cadenas de caracteres para indicar el inicio y generación de una excepción, respectivamente.


Con la invocación de File.WriteAllLines (línea 35) escribimos en el archivo log.txt todos los elementos contenidos en el arreglo eventos (e inclusive las cadenas vacías).

Agregamos una línea extra al archivo de registro de eventos para registrar el tiempo de fin de la aplicación (línea 39): File.AppendAllText(archivo, tiempoFin).

Finalmente, con las líneas 43-53 mostramos el contenido del archivo log.txt.


Compilación: 


  1. csc /target:exe UsoWriteAllLines.cs


Ejecución assembly: 


  1. .\UsoWriteAllLines.exe

> Prueba de ejecución: 
Ejecución assembly UsoWriteAllLines.exe

4. Práctica: Código C#

Crearemos una nueva aplicación consola para demostrar una capacidad interesante: filtro de registros con consulta LINQ [7].

En la línea 17 creamos una lista con objetos string para adicionar los registros de eventos. Líneas 20-27: Adición de registros de eventos de excepción y errores. Sobre la línea 30 invocamos al método File.WriteAllLines para escribir sobre el archivo log_completo.log todos los registros de eventos contenidos en registros.


En el caso de la línea 33, donde invocamos al método WriteAllLines al que especificamos como archivo destino log_errores.log y especificamos un filtro para escribir sólo los eventos de error con la expresión/consulta LINQ 

registros.Where( e => e.StartsWith("Error")


Con esta expresión estamos indicando que sólo se deben incluir aquellas cadenas de caracteres que empiecen con "Error".

Las líneas restantes (líneas 36-49) se encargan de mostrar el contenido de los archivos log_completo.log y log_errores.log.


Compilación: 


  1. csc /target:exe FiltroRegistros.cs


Ejecución assembly: 


  1. .\FiltroRegistros.exe

> Prueba de ejecución: 
Ejecución assembly FiltroRegistros.exe
Figura 3. Ejecución assembly FiltroRegistros.exe.

5. Conclusiones

Podemos concluir que el método File.WriteAllLines provee flexibilidad para la escritura de archivos de registros de eventos; además, con la combinación de LINQ es posible crear expresiones para filtrar el contenido de los registros de eventos y crear archivos con la información de interés.


En la próxima receta C# estudiáremos un tema relacionado al que acabamos de estudiar: procesamiento de un archivo de registros de eventos.

6. Glosario

  • Archivo
  • Evento
  • Filtro
  • Log
  • Registro

7. Literatura & Enlaces

[1]: Visual C# 2010 Recipes by Allen Jones and Adam Freeman. Copyright 2010 Allen Jones and Adam Freeman, 978-1-4302-2525-6.
[2]: File.WriteAllLines Method (System.IO) - https://msdn.microsoft.com/en-us/library/system.io.file.writealllines(v=vs.110).aspx
[3]: IEnumerable(T) Interface (System.Collections.Generic) - https://msdn.microsoft.com/en-us/library/9eekhta0(v=vs.110).aspx
[4]: File.WriteAllLines Method (String, String[], Encoding) (System.IO) - https://msdn.microsoft.com/en-us/library/3det53xh(v=vs.110).aspx
[5]: File.WriteAllLines Method (String, IEnumerable(String), Encoding) (System.IO) - https://msdn.microsoft.com/en-us/library/dd383463(v=vs.110).aspx
[6]: File.AppendAllText Method (String, String) (System.IO) - https://msdn.microsoft.com/en-us/library/ms143356(v=vs.110).aspx
[7]: Language Integrated Query - Wikipedia, la enciclopedia libre - https://es.wikipedia.org/wiki/Language_Integrated_Query


V