//Thread-safe example public class MyApplication { public static void main(String[] args) { JFrame f = new JFrame(...); ...//Add components to the frame here... f.pack(); f.setVisible(true); //Don't do any more GUI work here. } ... //All manipulation of the GUI -- setText, getText, etc. -- //is performed in event handlers such as actionPerformed(). ... }Sin embargo, si nuestro programa crea threads que realizan tareas que afectan al GUI, o manipulan un GUI ya visible en respuesta a algún evento del AWT, ¡sigue leyendo!
La Regla de los Threads es la siguiente:
Regla: Una vez que se haya realizado un componente Swing, todo el código que pudiera afectar o depender del estado de ese componente debería ejecutarse en el thread de despacho de eventos.
Esta regla podría sonar espeluznante, pero para la mayoría de los programas sencillos, no tenemos que preocuparnos de los threads. Antes de entrar en detalles sobre cómo escribir código Swing, definiremos dos términos: realizado y thread de despacho de eventos.
Realizado significa que el método paint del componente haya sido o podría ser llamado. Un componente Swing que sea una ventana de alto nivel se realiza habiendo llamado a uno de estos métodos sobre ella: setVisible(true), show, o (lo que podría sorprendernos) pack. Una vez que una ventana se ha realizado, todos los componentes que contiene están realizados. Otra forma de realizar un componente es añadirlo a un componente que ya esté realizado.
El thread de despacho de eventos es el thead que ejecuta el código de dibujo y de despacho de eventos. Por ejemplo los métodos paint y actionPerformed se ejecutan automáticamente en el thread de despacho de eventos. Otra forma de ejecutar código en el thread de despacho de eventos es usar el método invokeLater de SwingUtilities.
Existen unas pocas excepciones a la regla de que todo el código que afecte a un componente Swing realizado debe ejecutare en el thread de despacho de eventos:
- Unos pocos métodos de threads seguros.
- En la Documentación del API Swing, los métodos de threads seguros están marcados con este texto:
Este método es seguro ante los threads, aunque muchos métodos Swing no lo son. Por favor lea Threads and Swing para más información.- Un GUI de una aplicación frecuentemente puede ser construido y mostrado en el thread principal.
- Mientras que no se haya realizado ningún componente (Swing o de otro tipo) en el entorno de ejecución actual, está construir y mostrar un GUI en el thread principal de una aplicación. Para ayudarnos a ver por qué, aquí hay un análisis del thread seguro de thread-safe example. Para refrescar nuestra memoria, aquí están las líneas más importantes del ejemplo:
public static void main(String[] args) { JFrame f = new JFrame(...); ...//Add components to the frame here... f.pack(); f.setVisible(true); //Don't do any more GUI work here. }
- El ejemplo construye el GUI en el thread principal. En general, podemos construir (pero no mostrar) un GUI en cualquier thread, mientras que no hagamos llamadas que se refieran o afecten a los componentes ya realizados.
- Los componentes en el GUI son realizados por la llamada a pack.
- Inmediatamente después, los componentes el GUI son mostrados con la llamada a setVisible (o show). Técnicamente, la llamada a setVisible es insegura porque los componentes ya han sido realizados por la llamada a pack. Sin embargo, como el programa no ha hecho visible el GUI todavía, es sumamente contrario a que una llamada a paint ocurra antes de que retorne setVisible.
- El thread principal no ejecuta código GUI después de llamar a setVisible. Esto significa que el código del GUI se mueve del thread principal al thread de despacho de eventos, y el ejemplo es, en la práctica, de thread seguro.
- Un GUI de un applet puede contruirse y mostrarse en el método init:
- Los navegadores existentes no dibujan el applet hasta después de que hayan sido llamados los métodos init y start. Así, construir el GUI en el método init del applet es seguro, siempre que no llames a show() o setVisible(true) sobre el objeto applet actual.
Por supuesto, los applets que usan componentes Swing deben ser implementados como subclases de JApplet, y los componentes deben ser añadidos al panel de contenido del JApplet, en vez de directamente al JApplet. Al igual que para cualquier applet, nunca deberíamos realizar inicialización que consuma mucho tiempo en los métodos init o start; en su lugar deberíamos arrancar un thread que realice las tareas que consuman tiempo.
- Los siguientes métodos de JComponent son seguros para llamarlos desde cualquier thread: repaint, revalidate, e invalidate.
- Los métodos repaint y revalidate envian peticiones para que el thread de despacho de eventos llame a paint y validate, respectivamente. El método invalidate sólo marca un componentes y se requiere la validación de todos sus ancestros.
- Oyentes que pueden ser modificados desde cualquier thread
- Es siempre seguro llamar a los métodos addListenerTypeListener y removeListenerTypeListener. Las operaciones de añadir/eliminar no tienen ningún efecto en el despacho de eventos,
La mayor parte del trabajo de post-inicialización de un GUI naturalmente ocurre en el thread de despacho de eventos. Una vez que el GUI es visible, la mayoría de los programas son conducidos por eventos como acciones de botones o clicks del ratón, que siempre son manejados por el thread de despacho de eventos.Sin embargo, algunos programas necesitan realizar algún trabajo de GUI no conducido por eventos, después de que el GUI sea visible. Aquí tenemos algunos ejemplos:
- Programas que deben realizar una operación de inicialización larga antes de poder ser usados:
- Esta clase de programas generalmente debería mostrar algún GUI mientras está ocurriendo la inicialización, y luego actualizar o cambiar el GUI. La inicialización no debería ocurrir en el thread de despacho de eventos; si no el repintado y el despacho de eventos se pararían. Sin embargo, después de la inicialización el cambio/actualización del GUI debería ocurrir en el thread de despacho de eventos por razones de seguridad con los threads.
- Programas cuyo GUI debe actualizarse como resultado de eventos no-AWT:
- Por ejemplo, supongamos que un programa servidor quiere obtener peticiones de otros programas que podrían estar ejecutándose en diferentes máquinas. Estas peticiones podrían venir en cualquier momento, y resultan en llamadas a algunos de los métodos del servidor en algún método thread desconocido. ¿Cómo puede el método actualizar el GUI? Ejecutando el código de actualización del GUI en el thread de despacho de eventos.
La clase SwingUtilities proporciona dos métodos para ayudarnos a ejecutar código en el thread de despacho de eventos:
- invokeLater: Pide que algún código se ejecute en el thread de despacho de eventos. Este método retorna inmediatamente, sin esperar a que el código sea ejecutado.
- invokeAndWait: Actúa igual que invokeLater, excepto en que este método espera a que el código se ejecute. Como regla, deberíamos usar invokeLater en vez de este método.
Para más informaicón sobre el uso de invokeLater y invokeAndWait, y otros trucos para esciribir programas multi-threads, puedes ver Cómo usar Threads.