martes, 14 de julio de 2015

Receta C# No. 5-9: Lectura Asincrónica de un Archivo

Índice

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

0. Introducción

Esta receta C# tiene como objetivo demostrar las capacidaes asincrónicas que provee .NET para lectura asincrónica de archivos. Demostraremos las capacidades que ofrece la clase FileStream para soportar la lectura de un archivo de manera asincrónica por medio de un ejemplo que lee fragmentos de un archivo a medida que el thread principal continua su ejecución sin ninguna interrupción (latencias que pudiera se pudieran producir en el medio de almacenamiento del archivo o de la red de datos).

1. Problema

Requerimos leer los datos de un archivo sin bloquear el thread de ejecución principal (e.g., Main).

2. Solución

.NET cuenta con los artefactos necesarios para solventar este requerimiento de lectura de un archivo sin interrumpir la ejecución del thread principal. Sabemos que la transferencia de datos, por ejemplo, a través de una red de datos propensa a latencias significativas puede afectar el desempeño y la experiencia de usuario de nuestras aplicaciones, por eso se recurre a la técnica de lectura asincrónica.

3. Discusión de la Solución

En particular, la clase FileStream provee los siguientes miembros para leer los datos de un archivo por bloques:
  • BeginRead, y 
  • EndRead.
A través de estos dos métodos es posible implementar la lectura asincrónica de un archivo. En esencia lo que tenemos que hacer para implementar esta técnica es seguir los siguientes pasos:
  • Crear un archivo de procesamiento asincrónico para lectura de un archivo por bloques de datos.
    • Lectura de un bloque de datos a través del uso de BeginRead.
    • Cuando se haya finalizado la lectura asincrónica, recuperar los datos leídos con la invocación de EndRead, y procesarla.
    • Este ciclo continua hasta finalizar el procesamiento de los datos restantes por procesar.
  • Crear una clase cliente para instanciar la clase de procesamiento y eventualmente ejecutar otras tareas relacionadas con la manipulación de datos sin generar interrupciones o congelamientos en la ejecución.

3.1 Método FileStream.BeginRead

El método BeginRead [3] posibilita la lectura asincrónica. Este método ha estado presente desde las primeras versiones de .NET hasta la versión 4.5. La continuación de este método, como se anuncia en [3], es por razones de soporte a código legado; en su lugar se recomienda la versión asincrónica ReadAsync.

Firma:

public virtual IAsyncResult BeginRead(
    byte[] buffer, 
    int offset, 
    int count, 
    AsyncCallback callback, 
   Object state
)

Descripción:
  • buffer: Búfer para localizar los datos leídos asincrónicamente.
  • offset: Punto de inicio de lectura en el búfer.
  • count: Cantidad máxima de bytes a leer.
  • callback: callback una vez se finalice la lectura.
  • state: Objeto particular para mantener el estado o pasar argumentos en una llamada a BeginRead.

3.2 Método FileStream.EndRead

Con EndRead [4] se indica la espera de finalización de una< lectura asincrónica. Este método, además, permite determinar efectivamente cuántos bytes fueron leídos desde el flujo.

(Este método también cuenta con la nueva versión asincrónica ReadAsync.)

Firma:

public virtual int EndRead(
    IAsyncResult asyncResult
)

Descripción:
  • asyncResult: Referencia la solicitud asincrónica pendiente por terminar.
Podemos recurrir a la nota dada en [1]:
"...Using these methods [BeingRead, and EndRead], you can read a block of data on one of the threads provided by the .NET Framework thread pool, without needing to directly use the threading classes in the System.Threading namespace."
Igualmente se recomienda [1]:
"...it's usually easy to encapsulate your asynchronous file reading code in a single class."

 4. Práctica: Código C#

Demostraremos las capacidades de lectura asincrónica de los métodos BeingRead y EndRead a través de este ejemplo que encapsula las operaciones de lectura asincróinica en una clase que llamaremos ProcesadorLecturaAsincronica. Luego crearemos una clase cliente para usar la clase anterior y ejecutar tareas en paralelo mientras la lectura asincrónica por bloques ocrre y finaliza.


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:


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
Figura 1. Ejecución assembly DemoLecturaAsincronica.exe.

5. Conclusiones

En esta receta hemos aprendido varias cosas: A usar los métodos BeginRead y EndRead para el procesamiento asincrónico de un archivo. Hemos visto que el procesamiento asincrónico posibilita la escritura de aplicaciones para la ejecución de múltiples tareas en modo paralelo sin afectar el rendimiento general de la aplicación o de deteriorar la experiencia del usuario. En el ejemplo mostrado en la sección práctica vimos cómo leer bloques de datos de un archivo sobre un thread, mientras que en otro thread ejecutamos otras tareas sin presentarse ninguna interrupción o ralentización.

6. Glosario

  • .NET
  • Archivo
  • Búfer
  • Flujo
  • Paralelo
  • Proceso asincrónico
  • Ralentización
  • Rendimiento

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]: FileStream Class (System.IO) - https://msdn.microsoft.com/en-us/library/system.io.filestream(v=vs.110).aspx
[3]: Stream.BeginRead Method (System.IO) - https://msdn.microsoft.com/en-us/library/system.io.stream.beginread(v=vs.110).aspx
[4]: Stream.EndRead Method (System.IO) - https://msdn.microsoft.com/en-us/library/system.io.stream.endread(v=vs.110).aspx
[5]: Stream.ReadAsync Method (System.IO) - https://msdn.microsoft.com/en-us/library/system.io.stream.readasync(v=vs.110).aspx


M

No hay comentarios:

Publicar un comentario

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