lunes, 6 de febrero de 2017

Buenas practicas con Async/Await

Con la aparición de los procesadores con varios núcleos se ha vuelto critico el saber manejar la multitarea. Desde la primera versión del Framework de .Net existe el espacio de nombre System.Threading, el cual se puede considerar un wrappear para acceder a las directivas de sincronización del sistema, acceder al pool de hilos o crear nuestros propios hilos.

Posiblemente con System.Threading se puede realizar cualquier tarea. Pero la dificultad de su uso y mantenimiento han invitado a crear otro tipo de librerías más sencillas.

Primero se abordo la multitarea de CPU. Con una nueva clase para manejar tareas que es Task, un controlador llamado Parallel y una serie de clases para manejar colecciones de forma asíncrona. Esto nos permite aprovechar los diversos núcleos de los procesador actuales. Permitiendo lanzar tareas en paralelo y sincronizarlas de una forma sencilla.

Pero había un escenario que seguia siendo complicado. Y con la aparición de los entornos táctiles se empezó a ver la importancia de esto. Me refiero a la posibilidad de no bloquear ciertos recursos de la máquina.

Es importante entender la diferencia entre los dos escenarios de los que hablo. No es lo mismo la multitarea de CPU que la multitarea de IO. Y es importante entenderla bien.En este articulo nos centraremos en la multitarea de IO, o como no bloquear recursos de la máquina. Para esto a partir del Framework 4.5 se introdujeron Async/Await, que nos facilitan menormente el trabajo.

Otro concepto que hay que entender bien es el contexto de dispositivo. Los contextos de dispositivos los crea Windows y nos permiten pintar sobre la pantalla u otro dispositivo. Tanto en WinForm como WCF solo disponen de un contexto de dispositivo por aplicación. Esto significa que solo puede pintar un hilo determinado sobre la pantalla.

Una forma de invocar al contexto de dispositivo es mediante los eventos comunes, como OnClick, OnMouseMove. Todos los eventos y sobrecargas de la clase Form (en WinForm) o Window en (WPF) se invocan a traves del contexto de dispositivo. Otra forma de invocarlo es mediante el método Invoke (en WinForm) o a traves de la clase  Dispatcher en (WPF). En todos los casos hay que seguir una regla de oro. Mantener el hilo ocupado el menor tiempo posible.

Una casa que hay que entender de Async/Await, es que en principio nuestra aplicación no va a funcionar más rápido. Ya que Async/Await creará hilos y bloqueos por nosotros y estos consumen recursos del sistema. Así que solo usarlo donde sea necesario y se produzcan bloqueos de IO, como acceso a base de datos, a disco u servicios Web.

Async/Await hay que llevarlo hasta el final. Por ejemplo, desde la captura de nuestro evento, hasta el acceso a la base de datos.

No utilizar Async/Await para realizar tareas costosas en las que interviene el procesador. Por ejemplo, eso es una manera muy sencilla de no bloquear nuestra aplicación con una tarea costosa. Pero hay otra formas más elegante, creando una buena arquitectura Modelo Vista ViewModel.

private async void Button_Click(object sender, RoutedEventArgs e)
{
    await Task.Run(() =>
    {
       // Tarea costosa.
    });
}
Si tenemos una clase intermedia que no va a gestionar ningún tipo de bloqueo no utilizar Async/Await. Recordar que el compilador meterá mucho código por nosotros que conlleva sobrecarga en el sistema.

private Task AccesoADatos()
{
   int i = 54;
   return MiDDBB.CreateQueryAsync(i);
}
en vez de

private async Task AccesoADatos()
{ 
     int i = 54;
     await MiDDBB.CreateQueryAsync(i);
}

jueves, 2 de febrero de 2017

WCF con Async/Await

Con el Framework 4.5 se introdujo un mecanismo para evitar los bloqueos de una forma sencilla. Aunque la principal motivación de esta mejora han sido los entornos táctiles en aplicaciones de cliente, también se pueden utilizar para no bloquear el pool de hilos de un web servicie.

Cuando se habla de multitarea se puede referir a dos conceptos diferentes que hay que tener bien claros.
  • Multitarea de CPU o multithreading, que consiste en particional una tarea para aprovechar los diferentes núcleos que dispone la máquina. Este tipo de multitarea no es la que vamos a tratar en este artículo, y no es recomendable utilizarla en una máquina dedicada a dar servicio a traves de la red.
  • Multitarea asyncronica IO, que consiste en no bloquear el procesador mientras se realizan tareas de entrada y salida de datos. Un tiempo en el que nuestra máquina no hace nada. Los casos típicos de estos bloqueos son el acceso a disco, consultas a bases de datos, comunicación con otras máquinas.
Los servidores de aplicaciones como el IIS, nos abstraen de la tarea de gestionar los hilos necesarios para contestar a las diversas llamadas que se realizan a nuestro servicio. Estos disponen de un pool de hilos que van contestando a las diversas llamadas. Pero cuando no hay hilos disponibles en el pool bloquean las llamadas entrantes dejándolas en espera.

Se puede modificar la capacidad del pool. Pero estos parámetros vienen ajustados de fabrica y se adaptan a nuestra maquina en función de su velocidad y número de hilos. Así que no es recomendable modificar estos parámetros. Aun así, os dejo una referencia de como escalar el pool de aplicación.

Una forma de hacer llamadas asíncronas a un servicio web, de la que no vamos a entrar en detalle, es crear un método que devuelva un objeto IAsyncResult, con un parámetro AsyncCallback. Con esto se consigue no bloquear al cliente. Pero el hilo del Pool del servidor se mantiene en uso en todo momento. Además hay que capturar eventos en el cliente, creando un código más confuso.

Otra forma muy sencilla y elegante de que se introdujo en el Framewokr 4.5 es mediante programación asíncrona con async/await. Esto nos permite liberar el hilo del pool para hacer ciertas tareas de entrada y salida y luego recuperarlo de una forma muy sencilla.

Vasta con definir nuestro método de servicio como async y devolver un objeto Task. Exactamente igual que se definen cual otro tipo de métodos asíncronos.

[ServiceContract]
public interface IOperacioens
{
     [OperationContract]

     Task AddObjet(object x);


     [OperationContract]
     Task<int> CountObjets();}

public class OperacioensNetService : IOperacioen
{
     public async Task AddObjet(object x);
     {
     }


     public async Task<int> CountObjets()
     {
         ....
         return number;
     }
}
De esta forma tan simple tendremos creado nuestro método asíncrono para poder ser consumido remotamente.

A la hora de invocarlo desde el cliente es igual de sencillo. Solo tendremos que utilizar await para invocarlo.  Lo que nos permitirá no dejar bloqueado el objeto Dispacher encargado de dibujar en la pantalla, mientras se espera a que conteste el método remoto.