viernes, 22 de abril de 2016

Receta Multithreading en C# No. 4-3: Cómo Combinar Dos Tareas

Índice

1. Introducción
2. Palabras Clave
3. Problema
4. Solución
5. Discusión de la Solución
5.1 Clase Task
5.2 Método ContinueWith
5.3 Enumeración TaskContinuationOptions
5.4 Enumeración TaskCreationOptions
6. Práctica: Combinación de Tareas con Task
7. Conclusiones
8. Literatura & Enlaces

1. Introducción

Una nueva receta multithreading en C# para comprender cómo combinar dos o más tareas. La combinación de tareas puede resultar útil para la ejecución de procesos de dependencia jerárquica. La relación de dependencia se análoga con la jerarquía padre-hijo: mientras que una tarea finaliza su ejecución otra espera una señal de continuación para terminar con el ciclo de ejecución de un proceso.

2. Palabras Clave

  • Tarea
  • Tarea de continuación
  • Thread

3. Problema

Combinar dos o más tareas con dependencia en el orden de ejecución.

4. Solución

La clase Task cuenta con métodos para establecer una relación de jerarquía de ejecución entre tareas: permite combinar una tarea que dependa de otra en la ejecución de un proceso.

5. Discusión de la Solución

5.1 Clase Task

[Nota: Ver sección 5.1 de Receta Multithreading en C# No. 4-1: Cómo Crear una Tarea para conocer los esenciales de la clase Task.]

5.2 Método ContinueWith()

El método TaskContinueWith ("Task.ContinueWith Method", 2016) crea una tarea para ser ejecutada una vez finalizada la ejecución de otro proceso.

5.3 Enumeración TaskContinuationOptions

Con la enumeración TaskContinuationOptions ("TaskContinuationOptions Enumeration", 2016) se especifica el comportamiento para una tarea que se crea a partir de la invocación de TaskContinueWith.

5.4 Enumeración TaskCreationOptions

La enumeración TaskCreationOptions ("TaskCreationOptions Enumeration", 2016) permite especificar parámetros para la creación y ejecución de objetos Task.

6. Práctica: Combinación de Tareas con Task

En este ejemplo de código C# se presenta cómo combinar dos objetos Task a través del uso del método ContinueWith.

En las líneas 12 y 13 se crean dos objetos Task para la ejecución asincrónica del método Proceso() (líneas 96-107). Luego se invoca el método ContinueWith sobre el objeto tareaNo1 para crear una tarea de continuación al finalizar tareaNo1: muestra el valor entero retornado por la ejecución asincrónica de Proceso().


Al invocar Start sobre tareaNo1 y tareaNo2 se inicia la ejecución asincrónica del método Proceso() en threads de ejecución distintos. Para lograr que la ejecución tenga efecto en la salida estándar de la consola, con el método Sleep (línea 31) se suspende la continuación de ejecución de Main.

Sobre las líneas 35-49 ocurren las siguientes acciones:
  • Líneas 35-42: crea un objeto Task de continuación para que una vez finalice la ejecución de tareaNo2 se muestre el valor calculado con t.Result.
  • Líneas 45-49: cuando el objeto Task que se creó en la línea 35 finalice su ejecución se muestra en la salida estándar el estado final de ejecución de la tarea de ejecución.
De nuevo con el método static Sleep se detiene la ejecución de Main hasta por dos segundos.

Para una demostración final de la dependencia jerárquica de ejecución de tareas, se crea otro objeto Task con tareas anidadas:
  • Líneas 60-63: tarea para ejecución anidada. Finalizada su ejecución se continua con la tarea padre -tareaNo3-.
  • Línea 66-68: tarea de continuación para tareaAnidada (línea 60).
El ciclo while (líneas 77-85) comprueba el estado de ejecución de tareaNo3. Por cada ciclo se imprime el estado de ejecución general. Los estados dependen de la ejecución de tareaAnidada y la tarea de continuación. Antes de terminar la ejecución de esta aplicación de prueba, se imprime el estado de ejecución final de tareaNo3.

Compilación: 

csc /t:exe CombinacionTareas.cs

Ejecución assembly

.\CombinacionTareas.exe

Demostración ejecución assembly (ideone.com): http://ideone.com/t61HU5

Demostración ejecución assembly (local)
Animación 1. Ejecución assembly CombinacionTareas.exe.

7. Conclusiones

Se ha comprendido el proceso de combinación de tareas usando diferentes enfoques: tareas de continuación y tareas anidadas. Estos mecanismos son relativamente sencillos y naturales respecto a las soluciones -recetas- presentadas en los capítulos 3 y 4: sincronización de threads y uso de pool de threads. Se notó además cómo parametrizar la ejecución de una tarea anidada y de continuación: modo sincrónico, por ejemplo.

La próxima receta multithreading demuestra cómo convertir una implementación en Asynchronous Programming Model (APM) al modelo de tareas.

8. Literatura & Enlaces

Agafonov, E. (2013). Multithreading in C# 5.0 Cookbook. United States: Packt Publishing.
Receta Multithreading en C# No. 4-1: Cómo Crear una Tarea (2016, abril 22). Recuperado desde: http://ortizol.blogspot.com.co/2016/03/receta-multithreading-en-csharp-no-4-1-como-crear-una-tarea.html
Task.ContinueWith Method (Action(Task)) (System.Threading.Tasks) (2016, abril 22). Recuperado desde: https://msdn.microsoft.com/en-us/library/dd270696(v=vs.110).aspx
Task.GetAwaiter Method (System.Threading.Tasks) (2016, abril 22). Recuperado desde: https://msdn.microsoft.com/en-us/library/system.threading.tasks.task.getawaiter(v=vs.110).aspx
TaskContinuationOptions Enumeration (System.Threading.Tasks) (2016, abril 22). Recuperado desde: https://msdn.microsoft.com/en-us/library/system.threading.tasks.taskcontinuationoptions(v=vs.110).aspx
TaskCreationOptions Enumeration (System.Threading.Tasks) (2016, abril 22). Recuperado desde: https://msdn.microsoft.com/en-us/library/system.threading.tasks.taskcreationoptions(v=vs.110).aspx


V

jueves, 21 de abril de 2016

Receta C# No. 6-7: Cómo Leer y Escribir XML sin Cargar el Documento Entero en Memoria

Índice

1. Introducción
2. Palabras Clave
3. Problema
4. Solución
5. Discusión de la Solución
5.1 Clase XmlReader
5.2 Clase XmlWriter
6. Práctica: Lectura y Escritura de un Documento XML con XmlReader y XmlWriter
7. Conclusiones
8. Literatura & Enlaces

1. Introducción

En esta nueva receta se demuestra cómo leer y escribir sobre un documento XML sin necesidad de cargar todo el documento sobre la memoria. El propósito es escribir código de manipulación XML más eficiente: en términos de uso de los recursos de cómputo -espacio de memoria-. Para esto se hace uso de clases alternativas a XmlDocument: XmlReader y XmlWriter.

2. Palabras Clave

  • Caché
  • Documento
  • Memoria
  • Nodo
  • XML

3. Problema

Leer y escribir en un documento XML usando un flujo de entrada y salida. Además, la lectura y escritura de un nodo debe realizarse de manera serializada: uno a la vez; esto con el propósito de hacer más eficiente el uso de memoria.

4. Solución

En la BCL se cuenta con las clases XmlReader y XmlWriter para solucionar este problema de uso de memoria: carga completa del documento XML en memoria.

5. Discusión de la Solución

5.1 Clase XmlReader

La clase abstracta XmlReader (namespace System.Xml) lee un documento XML nodo a nodo. Cuenta con un mecanismo para la lectura rápida, no requiere caché y la lectura sólo hacia adelante ("XmlReader Class", 2016). Además, la lectura de los nodos se hace de manera directa desde el flujo de entrada y salida. Este modo de funcionamiento recae en un mejor rendimiento, dado que el uso de memoria es inferior respecto al demandado por la clase XmlDocument. Sin embargo, hay que considerar que no cuenta con el mecanismo de navegabilidad que XmlDocument sí provee: recorrer el documento XML de acuerdo a filtros de búsqueda, por ejemplo.

XmlReader cuenta con métodos ReadX para leer el contenido del documento XML con diferentes tipos de datos y/o en modo sincrónico o asincrónico. 

5.2 Clase XmlWriter

Con XmlWriter ("XmlWriter Class", 2016) se escribe sobre un documento XML usando directamente un flujo de entrada y salida. Posee las mismas propiedades que XmlWriter:
  • escritura sin requerir caché, 
  • escritura sólo hacia adelante, 
  • escritura rápida.
Cuenta con métodos WriteX ("XmlWriter Class", 2016) para escritura asincrónica de elementos, atributos y valores de diferentes tipos de datos.

Con el método static Create() se crea un documento XML especificando una cadena de caracteres con el nombre del archivo:

XmlWriter xmlWriterDoc = XmlWriter.Create("literatura-2016.xml");

6. Práctica: Lectura y Escritura de un Documento XML con XmlReader y XmlWriter

En este código de ejemplo en C# (adaptado de Jones (2010)) se crea una aplicación de consola para leer y escribir un documento XML.

Con las líneas 13 y 14 se crea un documento XML llamado productos.xml. Con la instrucción de la línea 17 se inicia la escritura del documento. El primer elemento de este documento se nombrará como Productos. A partir de este elemento se anidan otros para referenciar cada producto del catálogo de productos; esto se lleva a cabo con la creación de dos elementos en las líneas 23-34.

El documento XML recién creado y con nuevos elementos se cierra con las instrucciones de las líneas 37-40.

Para la apertura del archivo se usa una instancia de FileStream (línea 47) y se pasa como argumento al método static Create() (línea 48). Con el ciclo while (líneas 51-71) se leen los nodos del documento XML: elementos, atributos y valores.

Compilación: 

csc /t:exe LecturaEscrituraXml.cs

Ejecución assembly

.\LecturaEscrituraXml.exe

Prueba ejecución assembly
Ejecución assembly LecturaEscrituraXml.exe

7. Conclusiones

En esta receta se ha comprendido cómo usar las clases XmlReader y XmlWriter para la lectura y escritura eficiente de un documento XML: a diferencia de XmlDocument estas clases no cargan el documento en memoria, con lo cual se disminuye considerablemente el espacio requerido en memoria.

8. Literatura & Enlaces

Jones, A., Freeman (2010). Visual C# 2010 Recipes: A Problem-Solution Approach. United States: Apress.
XmlReader Class (System.Xml) (2016, abril 21). Recuperado desde: https://msdn.microsoft.com/en-us/library/system.xml.xmlreader.aspx
XmlWriter Class (System.Xml) (2016, abril 21). Recuperado desde: https://msdn.microsoft.com/en-us/library/system.xml.xmlwriter(v=vs.110).aspx


V

viernes, 15 de abril de 2016

Cadenas de Caracteres y Manipulación de Texto en C# - Parte 5/5: Sistemas de Codificación de Texto y Unicode

Índice

1. Introducción
2. Palabras Clave
3. Sistemas de Codificación de Caracteres
3.1 Conjunto de caracteres
3.1.1 Unicode
3.1.2 ASCII
3.2 Codificación de texto
3.3 Categorías
3.3.1 Subconjuntos de Unicode
3.3.2 Esquemas de codificación estándar de Unicode
4. Sistemas de Codificación en C#
4.1 Obtención de un tipo de sistema de codificación
4.2 Codificación para archivos
4.3 Representación sobre arreglos de bytes
4.3.1 De string a bytes[]
4.3.2 bytes[] a string
4.4 Pares suplentes en UTF-16
5. Conclusiones
6. Literatura & Enlaces

1. Introducción

Última parte de la serie Cadenas de Caracteres y Manipulación de Texto en C#. En este artículo se presentan los aspectos generales de codificación de texto y el sistema de caracteres Unicode. Se describe las generalidades de Unicode y su contraste frente al clásico ASCII: espacio de caracteres. requerimiento de almacenamiento y soporte por parte de Microsoft .NET Framework. Aquí se trata, además, el proceso de obtención de objetos Encoding, manejo de entrada y salida en archivos, arreglos de bytes, y breve descripción de los sistemas codificación de caracteres disponibles en .NET.

2. Palabras Clave

  • .NET
  • Aplicación legado
  • ASCII
  • Codificador de texto
  • EBCDIC
  • Esquema de codificación
  • Sistema de codificación
  • Unicode

3. Sistemas de Codificación de Caracteres

3.1 Conjunto de caracteres

Un conjunto de caracteres es una representación de caracteres para texto o literales. Cada carácter tiene asignado un código único para ser referenciado en la unidad de asignación propia del conjunto o subconjunto de caracteres.

En la actualidad dos de los conjunto de caracteres mayoritariamente usados son Unicode y ASCII.

3.1.1 Unicode

Unicode ("Unicode", 2016) es considerado en la actualidad el conjunto de caracteres de mayor uso. Éste cuenta con una capacidad de asignación de un millón de caracteres distintos. De esta capacidad, sólo el 10% -100.000- ha sido asignado. Esta asignación corresponde con los siguientes subconjuntos de caracteres (Albahari, 2012)
  • Caracteres de casi todos los idiomas, 
  • Idiomas de culturas antiguas, 
  • Símbolos especiales

3.1.2 ASCII

ASCII es considerado uno de los sistemas de codificación de caracteres más antiguos. Tiene más 30 años existencia: su simplicidad reside en la representación de los caracteres del idioma inglés. Microsoft .NET Framework soporta este sistema y es un subconjunto de Unicode: exactamente los primeros 128 caracteres.

3.2 Codificación de texto

El proceso de codificación de texto asigna un carácter a partir de un código único a su representación binaria (Albahari, 2012). Este proceso es usado en .NET para la entrada y salida de archivos y flujos de datos binarios.

Para el último caso, un programa conocido como codificador de texto mapea el contenido de un archivo desde su representación binaria a la representación que admite Unicode para los tipos char y string.

3.3 Categorías

En Microsoft .NET Framework existen dos categorías para la clasificación de un sistema de codificación (Albahari, 2012):
  • Subconjuntos de Unicode
  • Esquemas de codificación estándar de Unicode

3.3.1 Subconjuntos de Unicode

Dos ejemplos particulares podrían ser ASCII y EBCDIC. Estos dos sistemas utilizan un juego de caracteres limitados: por ejemplo, ASCII usa un juego de caracteres de 128 -donde se incluye el alfabeto inglés, los números decimales 0-9, símbolos especiales, y otros caracteres especiales para señalización en aplicaciones legado.

3.3.2 Esquemas de codificación estándar de Unicode

Los esquemas se definen de acuerdo al manejo de espacio para la representación de caracteres. Aquí se pueden citar los siguientes esquemas (Albahari, 2012):
  • UTF-32
  • UTF-16
  • UTF-8
  • UTF-7 (obsoleto)
De estos esquemas el más utilizado es UTF-8, debido a que para asignar memoria a un carácter se puede optar entre 1 y 4 bytes para los juegos de caracteres de los diferentes idiomas existentes en el mundo. En el caso particular de ASCII, sólo es requerido 1 byte para cada carácter de los 128 disponibles.

Vale mencionar que UTF-8 es el esquema que utiliza .NET para el manejo de flujos de entrada y salida; y que además constituye el sistema mayormente usado para la codificación de archivos de texto en aplicaciones que funcionan en Internet.

En cuanto a UTF-32 y UTF-16, cada uno de estos utiliza 2 y 4 bytes respectivamente. UTF-32 no resulta eficiente en espacio utilizado por carácter, sin embargo, como se menciona en Albahari (2012), UTF-32 es eficiente para acceso aleatorio a cada carácter; esto porque cada carácter tiene el mismo tamaño en memoria.

4. Sistemas de Codificación en C#

4.1 Obtención de un tipo de sistema de codificación


En C# se cuenta con el método static Encoding.GetEnconding() ("Encoding.GetEncoding Method", 2016) que cuenta con varias versiones sobrecargadas para obtener un objeto Encoding.
Métodos sobrecargados Encoding.GetEncoding
Figura 1. Métodos sobrecargados Encoding.GetEncoding ("Encoding.GetEncoding Method", 2016).
Para comprender su uso básico se recurre a la versión con firma GetEncoding(String). Este es un ejemplo de uso que recupera el esquema de codificación UTF-8 y GB18030:

Encoding utf8 = Encoding.GetEncoding("utf-8");
Encoding gb18030 = Encoding.GetEncoding("GB18030");

Por otra parte, es necesario saber que la clase abstracta Encoding cuenta con propiedades para obtener objetos particulares de esquemas (Albahari, 2012).
Propiedades static de Encoding
Figura 2. Propiedades static de Encoding (Albahari, 2012).
El método GetEncodings() obtiene la lista de todos los sistemas de codificación soportados por Microsoft .NET Framework. Este fragmento de código obtiene toda la lista completa:

foreach(EncodingInfo encoding : Encoding.GetEncodings())
{
Console.WriteLine (encoding.Name);
}

Demostración de ejecución online (ideone.com): http://ideone.com/iUMdKm

4.2 Codificación para archivos y flujos de entrada y salida

.NET Framework usa por defecto el esquema de codificación UTF-8 para flujos de entrada y salida. Un objeto de tipo de dato Encoding es el encargado de controlar el la escritura y lectura sobre un archivo.

En la siguiente línea de código fuente se escribe el texto Recetas LINQ sobre un archivo de texto linq.txt usando el esquema de codificación UTF-8:

File.WriteAllText("Recetas LINQ", "linq.txt", Encoding.UTF8);

En caso de que se omita el tercer argumento, por defecto se utilizará UTF-8.

[Nota: En futuros artículos se ampliará el manejo de flujos de entrada y salida y su relación con los sistemas de codificación.]

4.3 Representación sobre arreglo de bytes

4.3.1 De string a byte[]

Un objeto Encoding puede representarse en un arreglo de bytes. En las siguientes líneas de código se escribe la cadena de caracteres con los números de la base decimal sobre diferentes esquemas de codificación:

byte[] bytesUtf8 = Encoding.UTF8.GetBytes("1234567890");
byte[] bytesUtf16 = Encoding.Unicode.GetBytes("1234567890");
byte[] bytesUtf32 = Encoding.UTF32.GetBytes("1234567890");

Console.WriteLine (bytesUtf8.Length); // 10
Console.WriteLine (bytesUtf16.Length); // 20
Console.WriteLine (bytesUtf32.Length); // 40

Nótese en estas últimas tres líneas que el esquema más eficiente es UTF-8: sólo ocupa 10 bytes; UTF-16 el doble -20 bytes- y UTF-32 4 veces más.

4.3.2 byte[] a string

Un arreglo de elementos tipo byte puede ser representado como una cadena de caracteres; así:

string textoFuente1 = Encoding.UTF8.GetString(bytesUtf8);
string textoFuente2 = Encoding.Unicode.GetString(bytesUtf16);
string textoFuente3 = Encoding.UTF32.GetString(bytesUtf32);

Console.WriteLine (textoFuente1);
Console.WriteLine (textoFuente2);
Console.WriteLine (textoFuente3);

Cada invocación a WriteLine() escribe el texto original, es decir 1234567890.

4.4 Pares suplentes en UTF-16

El concepto de pares suplentes (surrogate pairs) describe el requerimiento de almacenamiento por parte del esquema de codificación UTF-16. .NET Framework usa este esquema para almacenar caracteres individuales o cadenas de caracteres: un carácter puede requerir una o dos palabras ("Palabra (informática)", 2016) de 16 bits cada una, y considerando que el tipo char tiene como capacidad 16 bits entonces se hace necesario almacenamiento extra para la representación de caracteres Unicode que requieren hasta dos (2) unidades char para ser presentados.

5. Conclusiones

En este artículo C# se ha comprendido el concepto de sistema de codificación de texto. Se estudió este concepto describiendo cómo .NET usa el sistema Unicode para representar los caracteres de una cadena de caracteres , un archivo de texto. Además se presentó las alternativas de esquemas de codificación disponibles: UTF-7, UTF-8, UTF-16 y UTF-32; cada uno de estos con distintos requerimientos de almacenamiento; siendo UTF-8 el más eficiente, y usado por defecto en .NET. Al final se presentaron fragmentos de código fuente C# para conversión de arreglo de bytes a string, y viceversa.


En el próximo artículo se estudia cómo representar fechas y horas utilizando tres estructuras disponibles en el namespace System.

6. Literatura & Enlaces

Albahari, J., Albahari, B. (2012). C# 5.0 in a Nutshell. United States: O'Reilly Media.
Unicode (2016, abril 10). Recuperado desde: https://en.wikipedia.org/wiki/Unicode
Encoding.GetEncoding Method (System.Text) (2016, abril 15). Recuperado desde: https://msdn.microsoft.com/en-us/library/system.text.encoding.getencoding(v=vs.110).aspx
Palabra (informática) (2016, abril 15). Recuperado desde: https://es.wikipedia.org/wiki/Palabra_(inform%C3%A1tica)
UTF-8 (2016, abril 15). Recuperado desde: https://en.wikipedia.org/wiki/UTF-8
UTF-16 (2016, abril 15). Recuperado desde: https://en.wikipedia.org/wiki/UTF-16
UTF-32 (2016, abril 15). Recuperado desde: https://en.wikipedia.org/wiki/UTF-32


V