Acerca de...
El equipo
Encuestas
WinTablets

Encuesta

Si las WINTABLETs no pudiesen ejecutar aplicaciones de escritorio, ¿las seguirías usando?

Cargando ... Cargando ...

últimas entradas importantes

Categorías

Archivos

13
Dic 2014
CrashesDesarrollorfogdevtrucos

No uses void en async

Cuando uno empieza con esto del async/await hay cosas que no tiene muy claras aunque lea en diez mil sitios cómo hacerlo. En su momento fue mi caso, y cometí muchos errores, más que por falta de conocimientos, por no haber entendido el modelo.

No os voy a explicar aquí qué pasa por dentro cuando uno hace una llamada a un método async mediante await, pero sí os voy a comentar el error más garrafal que se puede cometer en esta situación, y es usar un método async void.

Antes de empezar, una firma de un método asíncrono es:

async void MetodoAsync(){}

Esa sería la forma más sencilla y en teoría debería usarse cuando el método no devuelva nada. La firma para un método que devuelva algo debería ser

async Task<tipo> MetodAsync() {}

Veis que hemos cambiado el tipo de retorno por un Task<tipo> en el que tipo es la clase que vamos a devolver. Por ejemplo:

async Task<bool> MetodoAsync() {}

La firma de arriba nos devuelve un tipo lógico mediante un Task. Es decir, si nosotros llamamos al método así:

var result=await objeto.MetodoAsync();

Lo que va a ocurrir es que la ejecución de nuestro código esperará a que se ejecute MetodoAsync() y devuelva el valor booleano adecuado, que se asignará a result.

Si no esperamos nada, es decir, si el método no retorna ningún objeto, nuestra llamada sería

await objeto.MetodoAsync();

Y el sistema esperará a que se ejecute el interior de MetodoAsync() sin bloquear la interfaz. Esto se consigue construyendo una máquina de estados y si algún día tengo tiempo, os contaré cómo funciona de verdad, aunque es algo que no está documentado.

¿Por qué no podemos utilizar la primera firma, la que tiene el tipo void? ¿Por qué lo deja Microsoft si no debemos usarlo?

Lo primero sería preguntarnos si están tontos estos de Microsoft permitiendo algo sin emitir un triste warning. La verdad sea dicha, un poco tontos sí que están, pero no por eso. :-P

Las razones son históricas. Cuando se crearon los eventos, seguro que no se pensó en que estos métodos pudieran existir, y de hecho los eventos como tales no pueden devolver ningún tipo de valor ya que son, esto, eventos. Es decir, se ejecutan más o menos fuera de contexto por lo que hasta donde yo sé, no pueden almacenar nada en la pila para que sea recogido por algún llamante ya que su esencia es que son asíncronos y se lanzan de forma no sincronizada.

Por lo tanto, un evento no puede tener un tipo, y como Task<> es un tipo… Pues no puede haber eventos que devuelvan un Task, aunque sí pueden ser asíncronos.

Si nosotros definimos un evento de la forma clásica, es decir:

void MetodoEvento(object sender, EventArgs e);

Cuando el sistema (o nuestro código) llame a ese método, éste se ejecutará de forma síncrona. Como los eventos se suelen disparar desde el código de la interfaz (mayormente a través del XAML), la duración de ese evento será restada del tiempo de respuesta de nuestra aplicación y es cuando entramos en ese tipo de programas que no responden a nuestro dedo o que van a trompicones.

Por lo tanto, si tienes un evento que podría ser asíncrono, lo mejor es que uses el modelo await/async. No voy a entrar en el detalle de que un evento de la interfaz tarde micho tiempo en realizar una tarea aunque sea código chapucero. No voy a entrar porque yo también soy culpable y porque no estamos dilucidando ese tema.

Por lo tanto, una firma como esta:

async void MetodoEventoAsync(object sender, EventArgs e);

Lo que hará es lanzar el evento de forma asíncrona, colocándolo en la “cola de eventos asíncronos” y la interfaz seguirá con su trabajo mientras el código se ejecuta dentro de una máquina de estados.

Tampoco vamos a entrar ahora en qué ocurriría si la parte visual a la que hace referencia el evento deja de estar presente, o si ese evento hace referencia a otro elemento visual que también ha desaparecido.

Por tanto, la única justificación de la existencia de un método asíncrono con tipo de devolución void es esa.

¿Pero qué ocurre si nosotros llamamos a ese código desde el nuestro? Algo así:

await objeto.MetodoEventoAsync(this, e);

En principio nada malo. El código se ejecuta y el flujo del programa sigue su curso.

¿Y si se produce una excepción? Ah, amigo, ese es el busilis del asunto. Si se produce una excepción ahí dentro, lo que pasa es que básicamente se va todo al carajo y tu tienes un unhandled_exception a nivel de aplicación.

Vale, pues pongo un bloque try/catch dentro del método y pillo cualquier cosa que ocurra.

Pues no, eso tampoco funciona.

Ponerlo lo puedes poner, pero ese catch jamás pillará una excepción ni tendrá utilidad alguna.

Sí, en su momento yo también me pregunté “¿Comorrrrrrrrr?”.

Si os dais cuenta, a lo largo de la entrada he comentado varias veces algo sobre “máquina de estados”. Esa máquina de estados tiene lo que se llama un SynchronizationContext. Bueno, lo tiene si tu método devuelve un objeto de tipo Task. Si no, no lo tiene.

Y si no lo tiene, esa excepción queda sin controlar y se va al manejador unhandled, que generalmente te cerrará el programa.

Harina de otro costal, es entender ese tipo de excepciones, ya sea cuando se recoge en el SynchronizationContext como en el unhandled. En general hacen referencia a métodos que tu no has creado pero que están dentro de tu clase, que son los métodos de la máquina de estados (en general, algo con el nombre de MoveNext<bla>() ).

Si tienes suerte y si has hecho bien tu trabajo, tendrás un InnerException conteniendo la excepción real que generó la asíncrona que has recibido.

Ya os adelanto que algunas veces no es así y uno tiene que hacer muchas conjeturas para adivinar dónde se equivocó.

¿Y en los eventos, cómo funciona esto? Pues resulta que los eventos tienen su propio SynchronizationContext y ahí es donde se recoge la excepción, aunque por experiencia propia, terminas con un cierre inesperado de la aplicación.

Bienvenido al fácil mundo de la asincronía según Microsoft.

Hala, ya tienes un lugar donde buscar esas excepciones que recoge tu aplicación y no sabes de dónde vienen, ni por qué vienen, ni de qué código vienen.

Por RFOG | 1 Comentario | Enlaza esta entrada

Un Comentario

José Antonio
Enviado el 14/12/2014 a las 22:56 | Permalink

Buenas,

Muy bueno. Es cierto que eso pasa y es que usar async en un void es la forma más intuitiva de hacerlo. Por lo menos yo lo hice, aunque no estaba seguro si era lo más correcto y me costó un tiempo darme cuenta de que no solo era incorrecto, si no que era peligroso . A destacar la serie de videos http://channel9.msdn.com/Series/Three-Essential-Tips-for-Async

un saludo,
José Antonio

Deja un comentario  

Tu email nunca se publica o se comparte. Los campos obligatorios están marcados con *

*
*
:wink: :-| :-x :twisted: :) 8-O :( :roll: :-P :oops: :-o :mrgreen: :lol: :idea: :-D :evil: :cry: 8) :arrow: :-? :?: :!:
Puedes usar las siguientes etiquetas y atributos HTML:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

contacto@wintablet.info tema WinTablet.info por Ángel García (Hal9000)