jueves, 31 de marzo de 2016

Java Swing: Event Dispatch Thread (EDT) - Parte 1

Introducción


Si has programado Interfaces Gráficas de Usuario (GUI: Graphical User Interface) en Java Swing (JFC/Swing), deberías saber qué es el Event Dispatch Thread (de aquí en adelante EDT), pero tampoco me extrañaría que no hayas oído hablar de él.

Desarrollando una GUI en Java es típico preguntarse por qué durante el tratamiento de un evento (por ejemplo, la pulsación de un botón) pasan cosas como:
  1. que no aparece un cambio visual (texto, color...) que se trata de mostrar transitoriamente.
  2. que la interfaz se queda bloqueda/congelada/colgada (blocks/hungs/freezes).
La respuesta a ese tipo de preguntas se vuelve obvia cuando se conoce el modelo de hilos (hebras o threads) de ejecución que utiliza Swing.

El problema es que la mayoría de introducciones, tutoriales y guías rápidas de Swing no suelen hablar del EDT o no lo hacen entre los primeros puntos (en los tutoriales oficiales de JFC/Swing es el 4º y con el nombre que tiene, al menos a mí, no me dan ganas de leerlo para aprender a poner botones en pantalla), lo que suele provocar confusión en los programadores cuando empiezan a hacer cosas relativamente complejas.

Muchas de las cosas que comentaré sobre este tema se deben a mi experiencia usando Swing, y no a que yo tenga conocimientos de cómo funciona internamente.

¿Qué es y cómo funciona el Event Dispatch Thread?


El EDT es el hilo en el que la máquina virtual Java lleva a cabo todos las operaciones relacionadas con la GUI, como pintar componentes (objetos de la interfaz como botones, etiquetas, etc.) y atender los eventos de ratón y teclado (la exigua explicación del tutorial oficial aquí).

He subrayado pintar porque el proceso de pintar en pantalla también lo lleva a acabo el EDT, es decir, que en un instante determinado, o bien se está pintando en pantalla o bien se están atendiendo eventos, pero no ambas cosas a la vez.

Sabiendo eso, es muy fácil responder las preguntas anteriores:
  1. Al pulsar un botón no se puede mostrar el texto Cargando, realizar una carga lenta de datos y finalmente mostrar Cargado pensando que se llegará a ver el texto Cargando, porque el EDT no podrá repintar la pantalla hasta que finalice el evento completo, mostrando únicamente el resultado final Cargado pero no los posibles estados intermedios.
  2. Mientras el EDT esté atendiendo un evento, no podrá atender ningún otro ni refrescar la pantalla, por lo que si el evento tarda mucho tiempo el programa dará la sensación de estar colgado.
En esencia, el funcionamiento del EDT consiste en procesar, uno a uno y en orden de llegada, los eventos que han sido encolados en una cola (First In First Out) de eventos, considerando (re)pintar la pantalla también como un tipo especial de evento que se dispara cuando se alteran las propiedades gráficas (texto, colores, tamaño, etc.) de algún componente. Dicha cola especializada para gestionar los eventos de la GUI es la clase java.awt.EventQueue, que incluye el método isDispatchThread(), aunque es más común usar los métodos wrapper de la clase javax.swing.SwingUtilities, como isEventDispatchThread().

Pero es muy importante tener en cuenta que no es sólo el EDT quien puede encolar eventos, por lo que aunque el EDT esté bloqueado, pueden seguir llegando eventos que se procesaran posteriormente.

El procesamiento de cada evento incluye notificar a los oyentes correspondientes. Por tanto, cuando el programador registra un oyente de cierto tipo de evento, debe tener en cuenta que ese código se ejecutará en el EDT, y que si el tratamiento del evento es muy largo (por ejemplo, realiza alguna operación de entrada/salida), tendrá la GUI bloqueada hasta que el código del oyente termine, no pudiendo procesar nuevos eventos ni notificar a otros oyentes (lo que no impide que se sigan encolando eventos).

¿Por qué no se bloquea todo mientras el EDT está ocupado?


Aunque el EDT esté bloqueado, se pueden seguir haciendo algunas cosas con la GUI como cambiar el tamaño de las ventanas, minimizarlas y maximizarlas, mover el ratón y pulsar el teclado.

No hay que olvidar que la máquina virtual Java es una aplicación que se ejecuta sobre un sistema operativo real, y en el caso de necesitar interfaz gráfica, también sobre un gestor de ventanas (Windows, Gnome, KDE, etc).

Hay que tener en cuenta que hay eventos de bajo nivel, como el movimiento del ratón, las pulsaciones en el teclado y el manejo de las ventanas que son controlados por el gestor de ventanas y notificados a la máquina virtual Java como si ésta fuese un oyente. Luego Swing transformará algunos de esos eventos de bajo nivel en eventos de más alto nivel como que se ha hecho clic en un botón y los notificará a la aplicación.

Los eventos de bajo nivel se seguirán produciendo y aquello que sea controlado por el gestor de ventanas seguirá siendo posible, aunque la aplicación no recibirá todos esos eventos, pero en cualquier caso, los recibidos sólo se podrán procesar cuando el EDT se desbloquee, con lo que parecerá que se recibieron inmediatamente todos seguidos. Por ejemplo, no se recibirán todos los eventos de tipo MouseEvent que debería recibir un MouseMotionListener.

Aún así, probablemente pasarán efectos gráficos extraños con las ventanas, como que queden áreas sin pintar si se agranda.

Continuará


En futuros artículos continuaré hablando sobre el EDT y pondré un pequeño programa con el que ilustrar algunas de las cuestiones relacionadas con él que explique en los artículos.

Enlaces


Enlaces de interés relacionados con este artículo:

(Actualizado 06/04/2016)

No hay comentarios:

Publicar un comentario