Al programar orientado a eventos puede haber una condición de carrera entre eventos. Esto sucede cuando el orden de procesamiento de eventos importa. Una solución es reducir la generación de eventos por los manejadores de eventos.
Por ejemplo, al programar una interfaz gráfica con ventanas, se genera un evento como «on windows moved» cuando se mueve una ventana. Imaginemos que esto pasa a una ventana «principal» cuyo manejador de evento debe mover de la misma forma sus «subventanas», por ejemplo los menús o barras de herramientas. Este cambio de posición añade nuevos eventos al final de la cola para repintar la pantalla. Antes de procesarlos, el usuario puede mover una de estas subventanas a otra posición, que será entonces alterada cuando se procesan los eventos generados por la ventana principal.
Este problema no está limitado a interfaces gráficas, sino en cualquier sistema que permite generar el mismo tipo de evento a dos actores diferentes; por ejemplos al usuario y al programa mismo. La programación orientada a eventos permite, en teoría, de poder generar tantos eventos como uno quiere. Pero uno puede perder el control por las condiciones de carrera cuando se permiten generar nuevo eventos por los manejadores de eventos, ya que estos pueden desencadenar una avalancha de eventos.
Para evitar interferencias indeseadas entre eventos se pueden aplicar dos trucos
- Cambiar datos dependientes suprimiendo la generación de eventos.
- Desechar eventos mientras queden eventos del mismo tipo en la cola
Los dos trucos aprovechan que sólo hay una cola de eventos y que los eventos se tratan de forma secuencial.
Suprimir la generación de eventos
Si quedamos en el ejemplo inicial, entonces el manejador del evento «on move» de la ventana principal cambiaría las posiciones de las subventanas por la misma distancia. La forma más simple es cambiar las propiedades «x» e «y» de estas ventanas. No obstante, justamente esto provoca un evento «on move» en cada subventana que se mete al final de la cola de eventos. Entre el principio y final de esta cola ya puede haber otro evento «on move» de la misma ventana generado por el usuario.
Esto no queremos. Por esto debemos buscar la manera de cambiar la posición de la subventana sin generar estos eventos. Dependiendo de la biblioteca que se usa, puede haber una función en lugar de una propiedad que permite cambiar los valores de la posición de una ventana sin generar un evento. Después tenemos que llamar a una función de tipo actualizar, es decir «aplicar los valores actuales a la ventana», para que nuestros cambios tengan efecto. Como todo esto sucede dentro del mismo manejador de evento de la ventana principal, no se aplica ningún otro evento «on move» mientras tanto. Con esto hemos evitado que un desplazamiento posterior interfiere.
¡Nótese que este sistema sobresalta el diseño orientado a eventos! Esto puede indicar un mal diseño. Además, el programado debe llamar «manualmente» a los manejadores internos del framework utilizado, porque el sistema ya no se da cuenta de los cambios de otro modo.
Por cierto, un evento de tipo «aplicar los valores actuales» nunca es peligroso ya que aplica los más actual, no importa cuántos eventos de cambio se ejecutaron o quedan por ejecutar. Es menos aconsejable enviar eventos con cambios relativos, porque el resultado final puede depender del orden de procesamiento de los eventos.
Desechar eventos
Desechar eventos sirve cuando vienen más eventos del mismo tipo que podemos procesar. Imaginemos un protector de pantalla que muestra mil moscas. Cada vez, cuando el simulador cambia alguna propiedad de una mosca, se genera un evento de repintar la pantalla. Imaginemos que esto no demanda demasiado durante el curso normal del programa, pero al inicializar o reconfigurar el color de una gran parte de las moscas hay mil eventos a la vez – y para cada una repintamos la pantalla cuando realmente sólo necesitamos pintarla una vez para todas.
Se podría introducir un modo especial en que las moscas no generan eventos de cambio como descrito en el apartado anterior. No obstante, esto destruye la arquitectura, en que el modelo de mosca no tiene por qué saber de su representación. Como el programa es generador y consumidor de los eventos a la vez, también tenemos control sobre lo que hay en la cola de eventos.
Cada evento tiene un número de serie. Para cada evento enviado, el programa guarda el último número de serie en una memoria central. El manejador de evento puede comparar el número de serie del evento recibido con el número del último enviado. Si coinciden sabe, que no queden más eventos de este tipo en la cola y aplica los valores actuales, es decir, repinta la pantalla. En el caso contrario echa el evento sin hacer nada, porque sabe que pronto llegará otra actualización. De este forma se repinta la pantalla sólo una vez en lugar de muchas.
A veces conviene no esperar realmente al último evento, para que el usuario vea una reacción del programa. Por ejemplo, no se desecha cada n-ésimo evento o el primer evento tras un cierto tiempo.
Conclusión
El manejo de eventos que genera eventos a su vez puede causar las condiciones de carrera típicas para programas de multi-hilo, aunque sólo haya una cola de eventos que se procesa de forma secuencial. La consecuencia pueden ser errores esporádicos y difíciles de reproducir.
Aquí he presentado dos trucos de como tener más control de lo que entra en la cola de eventos. La primera idea trata de reducir los eventos generados, la segundo idea es desechar eventos en los manejadores de evento.
No obstante, ten en cuenta que estos trucos destruyen la arquitectura orientada a eventos. Por eso, antes de aplicarlos, considera si son trucos y no chapuzas.