jueves, 3 de enero de 2013

Programación Asíncrona


Hola a todos en este post les voy a platicar un poco de la programación asíncrona, esta se puede entender cuando una operación de larga duración se ejecuta en otro hilo sin que esta operación haga un bloqueo en la aplicación. Algunas operaciones que pudieran requerir de un tiempo de ejecución largo y que sería conveniente programarlas de manera asíncrona serían los operaciones como las de acceso al disco, peticiones en red, lectura y escritura de archivos, etc. 


Probablemente ya hemos utilizado programación asíncrona en alguna ocasión, tal vez utilizamos por ejemplo un ThreadPool para alguna operación, donde el hilo principal era liberado y podía seguir ejecutando las siguientes tareas.

Sin embargo la parte más difícil es que seguramente queremos saber cuándo una operación asíncrona haya finalizado. Para esto requeríamos hacer más cosas, esto se puede resolver en el código de bloqueo porque los métodos se ponen en el orden de ejecución, la contra es el bloqueo de la interfaz. En el mundo asíncrono esto no trabaja así, ya que seguramente la siguiente línea va a correr antes de que el método asíncrono termine.

Para resolver esto se han tenido que utilizar patrones para ejecutar código después de que una operación de segundo plano se complete, Insertando el código en la operación de segundo plano después del principal de la operación, suscripción de un evento que se activa al finalizar, Delegado o lambda para ejecutar después de la finalización (call back).

Si la siguiente operación se necesita ejecutar en un hilo en particular, también se necesita lidiar con la operación de gestión de colas en ese hilo. Todo esto ya se torna más desordenado y complejo.
Actualmente nos encontramos con los modificador Async y Await que son fundamentales para la programación asincrónica. El modificador async indica al compilador que un método o expresión lambda es asincrónico y se pueden usar operadores await para designar puntos de suspensión dentro del método. Este tipo de método es conocido como método asincrónico.

Dentro de un método async, se pueden aplicar operadores await a tareas particulares para suspender la ejecución del método asincrónico hasta que la tarea esperada termine. Mientras tanto, se devuelve el control al invocador del método asincrónico. La suspensión no representa una salida del método asincrónico y los bloques finally no funcionan.

Estas características hacen que la programación asíncrona sea más fácil eliminando la necesidad de implementar algún patrón de mayor complejidad. Ya que el compilador hace el trabajo difícil que el desarrollador hacía antes, incluyendo la firma de las continuaciones para la finalización del método suspendido. Como resultado, el código asincrónico es mucho más fácil de escribir y el programa mantiene una estructura lógica que es similar al código sincrónico. Por ejemplo, algunos procesos de rutina, tales como ciclos y manejo de excepciones, pueden ser difíciles de escribir en código asincrónico tradicional. En un método async, se pueden escribir estos elementos como se haría en una solución sincrónica y se resuelve este problema.
 
Veamos un ejemplo entre código de bloqueo y asíncrono con un ejemplo sencillo.
 
1) Abrimos Visual Studio y creamos un proyecto en winform.
2) Agregamos un botón y un listbox a la interfaz, queda algo así.
 
 
3) Agregamos el siguiente código.
 
 
        private void button1_Click(object sender, EventArgs e)
        {
            this.Ejecutar();
        }

        private void Ejecutar()
        {
            ProcesoSync(@"Z:\New folder.rar");
            list1.Items.Add("Se termino de procesar el archivo de forma sincrona....");
            ProcesoAsync(@"Z:\New folder.rar");
            list1.Items.Add("1 UI no bloqueada, continua ejecucion de codigo...");
            list1.Items.Add("2 UI no bloqueada, continua ejecucion de codigo...");
            list1.Items.Add("3 UI no bloqueada, continua ejecucion de codigo...");
            list1.Items.Add("4 UI no bloqueada, continua ejecucion de codigo...");
        }
 
        void ProcesoSync(string archivo)
        {
            byte[] buffer = null;
            try
            {
                using(var FsRead = new FileStream(@archivo, FileMode.Open, FileAccess.Read))
                {
                    list1.Items.Add("En lectura sync");
                    var binReader = new BinaryReader(FsRead);
                    long fileLen = new FileInfo(archivo).Length;
                    buffer = binReader.ReadBytes((Int32)fileLen);
                    binReader.Close();
                }

                using (var FsWrite = new FileStream(@"ArchivoSync.rar", FileMode.CreateNew, FileAccess.Write))
                {
                    list1.Items.Add("En escritura sync");
                    FsWrite.Write(buffer, 0, buffer.Length);
                }
           
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        async void ProcesoAsync(string archivo)
        {
            try
            {
                list1.Items.Add("Comienza proceso Async");
                byte[] buffer = null;
                using (Stream streamRead = new FileStream(@archivo, FileMode.Open, FileAccess.Read))
                {
                    list1.Items.Add("En lectura Async");
                    buffer = new byte[streamRead.Length];
                    Task ReadData = streamRead.ReadAsync(buffer, 0, (int)streamRead.Length);
                    await ReadData;
                }
               
                using (Stream streamWrite = new FileStream(@"ArchivoAsync.rar", FileMode.CreateNew, FileAccess.Write))
                {
                    list1.Items.Add("En Escritura Async");   
                    Task WriteData = streamWrite.WriteAsync(buffer, 0, buffer.Length);
                    await WriteData;
                    if (WriteData.IsCompleted)
                        list1.Items.Add("Se termino de procesar el archivo de forma Async....");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

4) Esto lo único que hace es que copia un archivo, pueden probar con un archivo grande para que se aprecie el ejemplo.
 
5) Ejecutan la aplicación.
 
se puede apreciar que cuando se esta ejecutando la parte síncrona la ventana esta pasmada esperando a que termine, una vez terminada manda al listbox una serie de mensajes, esto no pasa con la ejecución de la parte asíncrona ya que lo que esta leyendo el archivo y escribiendo esto lo hace en segundo plano y permite a la interfaz ejecutar las demás tareas que pudiera tener programada.
 
En resumen la programación asíncrona es importante y útil, sin embargo la importancia de su uso va en función del tipo de aplicación que se está desarrollando, por ejemplo, para el desarrollo de aplicaciones en Windows 8, la aplicación debe de cumplir con la parte de fluida y rápida por lo que el usuario poco o casi nada debería de notar estos procesos de larga duración.
 
sin mas espero les haya interesado el tema, un saludo y feliz año!!!