2010-03-20 8 views
12

Con la ayuda de las personas en stackoverflow pude obtener el siguiente código de funcionamiento de una cuenta regresiva simple de GUI (que solo muestra una ventana que cuenta en segundos). Mi principal problema con este código es el material invokeLater.¿Cómo funciona el hilo de envío de eventos?

Por lo que entiendo invokeLater, envía una tarea a la cadena de distribución de eventos (EDT) y luego la EDT ejecuta esta tarea siempre que "pueda" (lo que sea que eso signifique). ¿Es correcto?

A mi entender, el código funciona así:

  1. En el método maininvokeLater utilizamos para mostrar la ventana (showGUI método). En otras palabras, el código que muestra la ventana se ejecutará en el EDT.

  2. En el método main también iniciar el counter y el contador (por construcción) se ejecuta en otro hilo (lo que no es en el hilo del despacho de eventos). ¿Derecha?

  3. La counter se ejecuta en una secuencia separada y periódicamente llama a updateGUI. updateGUI se supone que debe actualizar la GUI. Y la GUI está trabajando en el EDT. Por lo tanto, updateGUI también se debe ejecutar en el EDT. Es la razón por la cual el código para updateGUI está encerrado en invokeLater. ¿Está bien?

Lo que no me queda claro es por eso que llamamos el counter de la EDT. De todos modos, no se ejecuta en el EDT. Comienza inmediatamente, se ejecuta un nuevo hilo y el counter allí. Entonces, ¿por qué no podemos llamar al counter en el método principal después del bloque invokeLater?

import javax.swing.JFrame; 
import javax.swing.JLabel; 
import javax.swing.SwingUtilities; 

public class CountdownNew { 

    static JLabel label; 

    // Method which defines the appearance of the window. 
    public static void showGUI() { 
     JFrame frame = new JFrame("Simple Countdown"); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     label = new JLabel("Some Text"); 
     frame.add(label); 
     frame.pack(); 
     frame.setVisible(true); 
    } 

    // Define a new thread in which the countdown is counting down. 
    public static Thread counter = new Thread() { 
     public void run() { 
      for (int i=10; i>0; i=i-1) { 
       updateGUI(i,label); 
       try {Thread.sleep(1000);} catch(InterruptedException e) {}; 
      } 
     } 
    }; 

    // A method which updates GUI (sets a new value of JLabel). 
    private static void updateGUI(final int i, final JLabel label) { 
     SwingUtilities.invokeLater( 
      new Runnable() { 
       public void run() { 
        label.setText("You have " + i + " seconds."); 
       } 
      } 
     ); 
    } 

    public static void main(String[] args) { 
     SwingUtilities.invokeLater(new Runnable() { 
      public void run() { 
       showGUI(); 
       counter.start(); 
      } 
     }); 
    } 

} 
+0

@Roman aquí hay una discusión más detallada: http://stackoverflow.com/questions/182316/java-swing-libraries-thread-safety – Kiril

+0

@Roman * Nota: * su contador no ** comienza ** en el EDT, comienza en el hilo principal. El contador actualiza la GUI a través del método updateGUI que realiza las actualizaciones en el EDT (debido a la llamada a invocarLater). – Kiril

Respuesta

16

Si entiendo bien su pregunta usted es de extrañar por qué no se puede hacer esto:

public static void main(String[] args) { 
    SwingUtilities.invokeLater(new Runnable() { 
     public void run() { 
      showGUI(); 
     } 
    }); 
    counter.start(); 
} 

La razón por la cual no se puede hacer es porque el programador no ofrece ninguna garantía ... sólo porque Invocó showGUI() y luego invocó counter.start() no significa que el código en showGUI() se ejecutará antes que el código en el método de ejecución counter.

creo que de esta manera:

  • invokeLater inicia un hilo y ese hilo es programa un evento asíncrono en el EDT, que se encarga de la creación de la JLabel.
  • el contador es un hilo separado que depende de la JLabel que existe lo que se puede llamar label.setText("You have " + i + " seconds.");

Ahora usted tiene una condición de carrera:JLabel deben crearse antes de las counter inicios de rosca, si no está creado antes de que comience el subproceso de contador, su subproceso de contador llamará al setText en un objeto no inicializado.

Con el fin de asegurar que la condición de carrera se elimina tenemos que garantizar el orden de ejecución y una manera para garantizarlo es ejecutar showGUI() y counter.start() secuencialmente en el mismo subproceso:

public static void main(String[] args) { 
    SwingUtilities.invokeLater(new Runnable() { 
     public void run() { 
      showGUI(); 
      counter.start(); 
     } 
    }); 
} 

Ahora showGUI(); y counter.start(); se ejecutan desde el mismo subproceso, por lo tanto, se creará el JLabel antes de que se inicie el counter.

Actualización:

Q:Y no entiendo qué tiene de especial este hilo.
A: El código de manejo de eventos de Swing se ejecuta en un hilo especial conocido como el hilo de envío de eventos. La mayoría del código que invoca los métodos de Swing también se ejecuta en este hilo. Esto es necesario porque la mayoría de los métodos de objetos Swing no son "seguros para la ejecución de subprocesos": al invocarlos desde múltiples subprocesos se corre el riesgo de interferencia de subprocesos o errores de coherencia de memoria. 1

Q:Por lo tanto, si tenemos una interfaz gráfica de usuario ¿Por qué debemos iniciarlo en un hilo separado?
A: Probablemente haya una mejor respuesta que la mía, pero si desea actualizar la GUI desde el EDT (que lo hace), entonces debe iniciarlo desde el EDT.

Q:¿Y por qué no podemos simplemente comenzar el hilo como cualquier otro hilo?
A: Ver respuesta anterior.

Q:Por qué utilizamos algunos invokeLater y por qué este hilo (EDT) comienza a ejecutar la solicitud cuando está listo. ¿Por qué no siempre está listo?
A: El EDT podría tener otros eventos AWT que deba procesar. invokeLater Hace que doRun.run() se ejecute de forma asincrónica en el subproceso AWT que distribuye el evento.Esto sucederá después de que se hayan procesado todos los eventos pendientes de AWT. Este método se debe usar cuando un subproceso de aplicación necesita actualizar la GUI. 2

+1

invokeLater no inicia un nuevo hilo. Más bien, programa Runnable para ejecutarse en el subproceso de despacho de evento AWT existente. –

+0

@Steve gracias, corregí la línea. Si observa en el último párrafo de la Q/AI, copió la documentación y dice específicamente: * "[invokeLater causes] doRun.run() se ejecutará de forma asincrónica en el hilo de envío del evento AWT." * Mi evaluación de la condición de carrera es cierto de cualquier manera. – Kiril

+0

No hay ninguna razón por la que no puedas usar invokeAndWait aquí, ¿verdad? Eso haría que el hilo actual espere hasta que se haya ejecutado el ejecutable. –

2

en realidad se está empezando la counter hilo de la EDT. Si llamó al counter.start() después del bloque invokeLater, es probable que el contador comience a ejecutarse antes de y la GUI se vuelva visible. Ahora, porque está construyendo la GUI en el EDT, la GUI no existirá cuando el counter comience a actualizarla. Afortunadamente parece que reenvía las actualizaciones de la GUI al EDT, lo cual es correcto, y dado que EventQueue es una cola, la primera actualización se realizará una vez que se haya construido la GUI, por lo que no debería haber ninguna razón para que esto no funcione. ¿Pero cuál es el punto de actualizar una GUI que puede no ser visible todavía?

+0

Gracias. Lo que dices tiene sentido para mí.Entonces, si comienzo el contador después de invokeLater después de un retraso, ¿debería funcionar? – Roman

+0

En realidad cometí un error en la primera versión de mi respuesta. Creo que debería funcionar en ambos sentidos. –

+0

@Roman No, aún no funcionaría ... un retraso no te da * una garantía *, simplemente te compra tiempo y todavía tienes la condición de carrera. No quiere ocultar las condiciones de su raza, quiere * eliminar * ellas. – Kiril

1

¿Cuál es el EDT?

Es una solución hacky torno a la gran cantidad de problemas de concurrencia que el API Swing tiene;)

En serio, una gran cantidad de componentes Swing no son "hilo de seguridad" (algunos programadores famosos fueron tan lejos como llamar oscilación "hilo hostil"). Al tener un hilo único donde se realizan todas las actualizaciones a estos componentes hostiles, se está eludiendo una gran cantidad de posibles problemas de concurrencia. Además de eso, también se le garantiza que ejecutará el Runnable que pase a través de él usando invokeLater en un orden secuencial.

Entonces algunos peros:

public static void main(String[] args) { 
    SwingUtilities.invokeLater(new Runnable() { 
     public void run() { 
      showGUI(); 
      counter.start(); 
     } 
    }); 
} 

Y luego:

En el método principal que también iniciar el contador y el contador (por construcción) se ejecuta en otro hilo (por lo no está en el evento envío de hilo). ¿Derecha?

Realmente no enciende el contador en el método principal. Inicia el contador en el método run() del anónimo Runnable que se ejecuta en el EDT. Entonces realmente comienza el contador Thread del EDT, no es el método principal. Entonces, como se trata de un subproceso separado, no es ejecutar en el EDT. Pero el contador definitivamente es iniciado en el EDT, no en el Thread ejecutando el método main(...).

Es quisquilloso, pero sigue siendo importante visto la pregunta, creo.

+0

WizardOfOdds, entiendo que el contador no se ejecuta en el EDT. Se ejecuta en un subproceso separado que (subproceso) se inicia desde el EDT. También entiendo que el contador no se ejecuta en el mismo hilo que el método principal. Solo quería decir que el método principal envió 'showGUI' y' counter.start' al EDT y 'counter.start' inicia un nuevo hilo desde EDT. – Roman

+0

@Roman: yup, exactamente ... Pero es importante obtener la redacción correcta en caso de que alguien más lea esta pregunta/respuestas :) – SyntaxT3rr0r

0

Esto es simple, es como sigue

Paso 1. El hilo inicial también llamado hilo principal se crea.

Paso 2. Cree un objeto ejecutable y páselo a invokeLate().

Paso 3. Esto inicializa la GUI pero no crea la GUI.

Paso 4. InvokeLater() planifica el objeto creado para su ejecución en EDT.

Paso 5. Se ha creado GUI.

Paso 6. Todos los eventos que ocurran se colocarán en EDT.

Cuestiones relacionadas