Pórtate bien con tu interfaz de usuario

Hola!

¿Cuántas veces habéis padecido una aplicación que al realizar una operación se queda “tostada”? Me refiero a esas operaciones que llevan un tiempo, transforman tu cursor en un reloj de arena, y cuando minimizas esa ventana (si se deja) y la restauras puedes contemplar el auténtico arte monocromático (es decir, que ves toda la ventana blanca, excepto los bordes).

Ese tipo de comportamientos debería estar prohibido. ¡Pro-hi-bi-do! Sobre todo con lo sencillo que es evitarlo usando .NET. Y este artículo va a explicar qué hemos de hacer para no volver a cometer tropelías del estilo nunca más. [;)]

Cuando en una aplicación estamos ejecutando nuestro código, salvo que creemos hilos, el código se ejecuta sobre el hilo principal de la aplicación. Este hilo principal, si no tocamos el código que genera Visual Studio amablemente, es el encargado de crear nuestras ventanas, nuestros controles, los inicializa, etc. Pero lo que es más importante, en ese hilo principal es donde se ejecuta el bucle de mensajes que mueve toda la maquinaria en las aplicaciones Windows.

Voy a comentar brevemente cómo funciona Windows por debajo de .NET (muy por encima, porque para explicarlo bien, bien, bien, tenéis el fantástico clásico de Charles Petzold, Programming Windows), para que veamos por qué es tan importante el hacer que el hilo principal de ejecución no se quede atascado con operaciones que puedan llevar un tiempo su ejecución.

Básicamente, a diferencia de la programación típica de aplicaciones de consola, el flujo de una aplicación Windows no lo dirigimos nosotros, sino que lo dirige el usuario. Nosotros le exponemos todo aquello que puede manipular (ventanas, botones, cajas de texto, etc), y esperamos a que el usuario interactúe con ellos, para generar una respuesta si nos conviene. Otros mensajes directamente podemos ignorarlos.

Esto se traduce a lo siguiente: Nosotros le pedimos a Windows que nos cree una ventana. Windows nos otorgará una cola de mensajes asociada a la ventana, y cualquier notificación que genere la interacción con el usuario (o procesos internos de Windows) que tengan que ver con nuestra ventana será colocada diligentemente por el sistema operativo en nuestra cola de mensajes. Estos mensajes son de todo tipo, desde “se está moviendo el ratón” a “alguien ha hecho click” o “tienes que repintarte”. Hmmm… Todo esto es muy similar a los eventos que conocemos desde hace mucho tiempo antes de .NET (Visual Basic, por ejemplo), ¿verdad?

Volviendo a .NET, cuando creamos un formulario, Windows creará la ventana, y le asocia una cola de mensajes. El hilo principal de ejecución del formulario estará constantemente revisando los mensajes que le envía el sistema operativo, y los traducirá a los eventos que nosotros podemos utilizar en .NET. Cuando llega ese evento, .NET llamará a las funciones que se encarguen de su gestión (Handles o AddHandler en VB.NET, Evento+=new Handler… en C#) en un orden no determinista.

El problema es que las llamadas a nuestros métodos se llevan a cabo de forma síncrona. Esto implica que si tardamos mucho en terminar, el hilo principal no podrá seguir despachando los mensajes. Con lo que no llegan mensajes de “tienes que repintarte”, “movieron el ratón”, “han hecho click” o “ha pasado x tiempo”. Esta es la razón por la que no se puede confiar en los timers para medir tiempo, por ejemplo.

¿Cuál sería la solución? Cuando tengamos una operación pesada en un evento, creamos un nuevo hilo, y ejecutamos la operación de forma asíncrona. Así el método de gestión del evento terminará rápidamente, y todos tan contentos.

Fácil, ¿verdad?

Bueno, esto tiene una pequeña pega. Si durante esa operación que nos hemos llevado a otro hilo tenemos que modificar algún control de usuario, el piñazo está asegurado (se lanzará una InvalidOperationException) ya que en Windows, los controles sólo se pueden modificar desde el mismo hilo en el que fueron creados. Y ese hilo es el principal (en el que NO estamos).

Para solucionar este pequeño problema, los controles y los formularios exponen una propiedad llamada InvokeRequired. Esta propiedad devuelve true si estamos en un hilo diferente al hilo principal, y false en caso contrario. Además, también exponen un método llamado Invoke, que se encarga de ejecutar el método que le indiquemos dentro del hilo principal. O si queremos lanzarlo de forma asíncrona, podemos utilizar el método BeginInvoke.

En el código adjunto podéis ver una aplicación muy sencilla en la que está implementado un ejemplo. El botón Sigues Vivo sirve para ver si se siguen procesando eventos.

Saludos!

Edito: Corregidos un par de fallos ortográficos, y un error de bulto. Caramelo para el que adivine cuál. (NO es en el código).

8 comentarios en “Pórtate bien con tu interfaz de usuario”

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *