miércoles, 11 de junio de 2014

Expresiones Lambda en C# - Parte 4: Expresiones Lambda y Asincronismo

Tabla de Contenido

0. Introducción
1. Introducción a async
2. Asincronismo y Expresiones Lambda
3. Conclusiones
4. Glosario
5. Literatura & Enlaces

0. Introducción

Esta cuarta parte estará constituida por otro tema muy llamativo e importante en la programación asincrónica: asincronismo y su combinación con expresiones lambda. Precisamente hablaremos del modificador async que se aplica a métodos, expresiones lambda, y métodos anónimos y permite su ejecución de forma asincrónica (ya explicaré este término). Mostraré varios ejemplos en donde ilustro su utilidad y eficacia, de tal manera que podamos producir código C# de mayor calidad y elegante.

1. Introducción a async

El modificador async [4] permite establecer o modificar la firma de un método formal (con nombre), una expresión lambda, o un método anónimo para que sea tratado como un método asincrónico. Un elementos de los anteriores es asincrónico si es capaz de ejecutarse en un plano distinto al hilo de ejecución desde donde es invocado; por ejemplo, si desde el método Main invocamos a un método de un objeto que se encarga de guardar un registro (log) que podría tardar varios segundos/minutos en completarse y aún tenemos varias tareas por realizar en el resto del código cliente que no son dependientes del volcado de los registros y que no queremos que nuestra aplicación se bloquee a razón del anterior, entonces podemos marcarlo como asincrónico para aprovechar otro plano de ejecución y nuestra aplicación no termine por congelarse.

En línea con lo anterior, en abstracto podemos ver la secuencia de ejecución de Main de la siguiente manera:

public static void Main()
{
sentencia1;
sentencia2;

// invocación de método con proceso largo:
GuardarRegistro();

// estas setencia no depende de `GuardarRegistro`:
sentencia3;
sentenciaN;
}

public async GuardarRegistro()
{
// Guardar registros
// proceso largo, que puede tomar
// varios segundos o minutos.
}

El método GuardarRegistro es candidato para ser convertido en un método asincrónico. ¿Cómo lo logramos? Especificando el modificador async delante del modificador de acceso, así:

public static async void GuardarRegistro()
{
// Implementación
}

Hay que agregar que un método marcado con async requiere en su cuerpo declarativo por lo menos una expresión o sentencia con await (este operador se utiliza para suspender la ejecución de instrucciones hasta que un método asincrónico no se haya completado).

Veamos un ejemplo (adaptado y traducido de [5]):

Archivo de código fuente UsoAsyncAwait.cs [enlace alternativo]:
En línea 13 utilizamos la clase Task<TResult> [6] para definir una tarea u operación asincrónica, en este caso se trata del método ProcesoDatosAsync. Este método (líneas 26-42) posee la siguiente firma:

public static async void ProcesoDatosAsync()


notemos que está siendo modificado con async para indicar que se trata de un método asincrónico. (El resto de elementos de la firma son los que conocemos hasta el momento.) Dentro de la declaración de este método creamos una nueva tarea (Task<TResult>) en la línea 30 que espera como tipo de retorno un tipo entero int; esto se alcanza a través de la invocación del método ProcesamientoArchivoAsync. Esta es su firma:

static async Task<int> ProcesamientoArchivoAsync(string archivo)

A tener en cuenta:
  1. Método asincrónico
  2. Tipo de retorno Task<int>
En la línea 35 mostramos un mensaje en la salida estándar para indicar al usuario del programa que se está llevando a cabo una tarea que toma varios segundos (o minutos dependiendo de qué tan grande sea el archivo que está procesando).

Por otro lado, en la línea 39 ocurre algo nuevo, la tarea que lleva a cabo el método ProcesamientoArchivoAsync debe finalizar antes de que podamos asignar un valor concreto a la variable int x. Miremos lo que ocurre dentro de ProcesamientoArchivoAsync para comprender lo que ocurre en segundo plano:
  1. Por motivos didácticos en la línea 49 mostramos un mensaje en la salida estándar para señalar que el método ProcesamientoArchivoAsync ya se encuentra en ejecución.
  2. Creamos un instancia StreamReader con el archivo especificado en la invocación de ProcesamientoArchivoAsync. (aquí está el enlace de descarga)
  3. Sobre la línea 59 invocamos al método asincrónico ReadToEndAsync() y esperamos a que se complete poniendo por delante al operador await.
  4. El resto de código de ProcesamientoArchivoAsync realiza crear un retardo simulado (líneas 66-74).
Compilación:


  1. csc /target.exe UsoAsyncAwait.cs

Ejecución:


  1. .\UsoAsyncAwait.exe

Prueba:
Prueba de ejecución de async y await
Figura 1. Prueba de ejecución de async y await.

[Nota: En futuros artículos y recetas, trataré con mayor lujo de detalles el uso de estas construcciones: async, y await.]

2. Asincronismo y Expresiones Lambda

¿En qué situaciones nos puede resultar apropiado el uso del asincronismo con expresiones lambda? Imaginemos un interfaz gráfica de usuario compuesta por varios comandos que invocan tareas que pueden tardar una gran cantidad de tiempo en completarse. Algo sencillo será el cálculo de la serie de Fibonacci para un número grande.

De igual manera, y como vimos en la anterior sección, el concepto de asincronismo puede ser aplicado a cualquier proceso que requiera bastante tiempo para ser completado o cualquier tarea lenta: descarga o subida de un archivo (esto depende del ancho de banda contratada), generación del log de una aplicación, generación de reporte con decenas de miles con los extractos de clientes de una entidad bancaria, &c.

Usemos un ejemplo en donde creemos un formulario muy sencillo con un campo de texto para introducir un número entero, un botón para activar el cálculo de la función Fibonacci, y una etiqueta para mostrar el resultado. Además, pondremos otro botón para mostrar la capacidad asincrónica, es decir, nuestro formulario no se debe congelar/bloquear mientras se realiza el cálculo.

Archivo de código fuente FormularioConAsync.cs [enlace alternativo]:
Observemos como en la línea 75 asignamos al evento Click una expresión lambda precedida por el modificador async:

async (sender, e) =>
{
Task<long> tarea = CalculoFibonacci (long.Parse(txtNumero.Text));

long fibonacci = await tarea;

lblResultado.Text = String.Format("Resultado: {0}", fibonacci.ToString());
}

Sobre el método asincrónico CalculoFibonacci (líneas 101-114) incluimos la lógica para realizar el cálculo de la serie Fibonacci; sin embargo, en la línea 111 se establece un retardo arbitario con el método Task.Delay [7] para simular un tiempo de espera.

La interfaz está compuesta por otro botón que nos muestra un mensaje cuando es pulsado. Al evento `Click` de este botón le hemos asignado una expresión lambda:

(sender, e) => 
MessageBox.Show("La interfaz no se congela mientras se calcula la serie Fibonacci.");

que lo único que nos muestra en pantalla es un mensaje en un cuadro de esto, nada nuevo, por supuesto; pero con el hecho que podamos activar el evento de este botón demostramos la capacidad asincrónica que nos entrega el uso del modificador async y el operador await sobre los métodos asincrónicos.

Compilación:


  1. csc /target:exe FormularioConAsync.cs

Ejecución:


  1. .\FormularioConAsync.exe

Resultado:

3. Conclusiones

En el desarrollo de este artículo apreciamos el poder de asincronización que podemos lograr a través del uso del modificador async sobre métodos y expresiones lambda. Este modificador colabora con el operador await. En el ejemplo de la sección 2 quedó explicito el uso de ambos a través de la construcción de un sencillo formulario con capacidades asincrónicas: los controles integrales no se bloquean mientras se lleva a cabo la operación larga Fibonacci. En el siguiente artículo, se hará la presentación de varios ejemplos para resumir el aprendizaje obtenido en los cuatro artículos acumulados hasta este punto.

4. Glosario

  • Asincrónico
  • Async
  • Await
  • Expresión lambda
  • Método formal
  • Método anónimo

5. Literatura & Enlaces

[1]: C# 5.0 in a Nutshell by Joseph Albahari and Ben Albahari. Copyright 2012 Joseph Albahari and Ben Albahari, 978-1-449-32010-2.
[2]: Asynchronous Lambda Expressions Archives - Umamahesh.net | UmaMahesh.Net - http://www.umamahesh.net/tag/asynchronous-lambda-expressions/
[3]: Lambda Expressions (C# Programming Guide) - http://msdn.microsoft.com/en-us/library/bb397687.aspx
[4]: async (C# Reference) - http://msdn.microsoft.com/en-us/library/vstudio/hh156513(v=vs.110).aspx
[5]: C# Async, Await Example - http://www.dotnetperls.com/async
[6]: Task(TResult) Class (System.Threading.Tasks) - http://msdn.microsoft.com/en-us/library/dd321424(v=vs.110).aspx
[7]: Task.Delay Method (Int32) (System.Threading.Tasks) - http://msdn.microsoft.com/en-us/library/hh194873(v=vs.110).aspx


J

No hay comentarios:

Publicar un comentario

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