domingo, 18 de agosto de 2013

async/await en C# 5.0

Personalmente no he hecho nada demasiado serio con C# (sí programo, pero no es ni cerca mi lenguaje principal), pero es un lenguaje que me resulta sumamente interesante. Ya había hablado en este blog sobre las mejoras que trajeron las versiones 3.0 y 4.0.

En la última versión de C#, la 5.0 que salió con VisualStudio 2012, se agregaron dos nuevos comandos: async y await. De hecho, es de las pocas cosas nuevas que vinieron con esta versión.

Debo reconocer que al principio no le di mucha importancia, hasta que leí este artículo de Miguel de Icaza que me dejó pensando en el tema: Callbacks as our Generations' Go To Statement

¿Cómo funcionan los comandos async/await? (1)

Cuando se declara un método como async, lo que le estamos diciendo al compilador es que el método puede detener su ejecución en cualquier momento mediante el uso de un comando await.

A su vez, un método declarado como async tiene que devolver un objeto de tipo Task o Task<T> según su valor de retorno "real" sea void o T.

Cuando un método llama a otro que fue declarado como async, tiene la opción de llamarlo y esperar prefijando el llamado con el comando await, o puede seguir ejecutando cosas que son independientes, y guardarse la Task devuelta por el método para esperar más adelante.

Algo así:
public void TestAsyncCall()
{
    Task t1 = SomeLongTaskAsync();
    await AnotherLongTaskAsync();
    await t1;
    ShowCompletionMessage();
}
Por último, cuando un método queda a la espera de un comando await, pasa la ejecución al llamador para que pueda continuar con otra cosa, o quedar a su vez esperando en otro await.

En principio, visto así, no parece ser algo que nos vaya a cambiar la vida. Pero si uno lo piensa en relación al trabajo que puede dar resolver estos temas de forma manual, se da cuenta con la simpleza que lo lograron resolver y cuanto trabajo puede ahorrar.

Comparación con Objective-C

En Objective-C, y en particular para iOS desde la versión 4 en adelante, lo más parecido que tenemos es Grand Central Dispatch.

Supongamos que queremos tener una tarea que ejecute en background, y luego mostrar un mensaje de que terminó. Para eso, tenemos que escribir algo así:
dispatch_queue_t current_queue = dispatch_get_current_queue();
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSError *error = [self someLongOperation];
    dispatch_async(current_queue, ^{
        [[UIAlertView alertForError:error] show];
    });
});
No es mala la solución, pero no tiene la simplicidad que tiene la de C#... Nótese que la mayor parte del código (en este caso, no siempre es así), es para decirle que ejecute de forma asincrónica. En C# lo escribiría tal vez así:
Error err = await SomeLongOperation;
ShowError(err);
Además puede tener algunos problemas.

Como estamos usando dispatch_async, si queremos esperar a que el llamado termine (como con el await), hay que hacerlo usando semáforos.

La alternativa podría ser usar dispatch_sync, pero en ese caso siempre estamos bloqueando, no tengo forma de seguir con ambas ejecuciones en paralelo. Además como el dispatch_sync puede usar el mismo thread de ejecución que el llamador, puedo potencialmente bloquear el thread de UI, cosa que no se debe hacer en ningún caso.

Conclusión

Me pareció una muy buena solución la de C#, creo que es bastante superior a las alternativas que se que tenemos en otros lenguajes.

También me resultó muy interesante el artículo de Miguel de Icaza. Si van al final del artículo hay un link a otro artículo que explica como otra persona implementó una especie de tutorial de su aplicación usando Mono y async/await. Vale la pena leerlo.

Me gustaría saber como se resuelve el tema de la ejecución asincrónica en otros lenguajes, así que agradezco cualquier comentario al respecto.

Actualización, 19/8/2013

Me corrige Fabián, y en parte tiene razón,  que GCD no es lo mejor para eso, que en Objective-C es más fácil usar NSOperationQueue.

Tiene razón en que es de más alto nivel que GCD, y facilita el tema de las dependiencias entre tareas (no se precisa usar semáforos), pero de todas formas se precisa bastante más código que en C# y la lógica queda partida en varios bloques, con saltos en la ejecución entre uno y otro.

En resumen, el argumento se mantiene: las construcciones async y await de C# facilitan mucho la tarea y hacen el código más fácil de leer.


(1) Es una interpretación libre de lo que pude leer e investigar... si me equivoco, agradezco me corrijan.

No hay comentarios.:

Publicar un comentario