Come funziona il thread di invio dell’evento?

Con l’aiuto di persone su StackOverflow, sono riuscito a ottenere il seguente codice di lavoro di un semplice conto alla rovescia della GUI (che mostra solo una finestra che conta i secondi). Il mio problema principale con questo codice è la roba invokeLater .

Per quanto comprendo invokeLater , invia un’attività al thread di invio eventi (EDT) e quindi EDT esegue questa attività ogni volta che “può” (qualunque cosa ciò significhi). È giusto?

A mio parere, il codice funziona in questo modo:

  1. Nel metodo main usiamo invokeLater per mostrare la finestra (metodo showGUI ). In altre parole, il codice che mostra la finestra verrà eseguito nell’EDT.

  2. Nel metodo main iniziamo anche il counter e il contatore (per costruzione) viene eseguito in un altro thread (quindi non si trova nel thread di invio dell’evento). Destra?

  3. Il counter viene eseguito in un thread separato e periodicamente chiama updateGUI . updateGUI dovrebbe aggiornare la GUI. E la GUI sta lavorando nell’EDT. Quindi, updateGUI dovrebbe anche essere eseguito updateGUI . È il motivo per cui il codice per updateGUI è racchiuso in invokeLater . È giusto?

Ciò che non mi è chiaro è il motivo per cui chiamiamo il counter dall’EDT. Ad ogni modo, non viene eseguito nell’EDT. Inizia immediatamente, un nuovo thread e il counter viene eseguito lì. Quindi, perché non possiamo chiamare il counter nel metodo principale dopo il blocco 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(); } }); } } 

Se capisco correttamente la tua domanda ti chiedi perché non puoi farlo:

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

Il motivo per cui non puoi farlo è perché lo scheduler non fornisce garanzie … solo perché hai invocato showGUI() e quindi hai invocato counter.start() non significa che il codice in showGUI() verrà eseguito prima il codice nel metodo di esecuzione del counter .

Pensare in questo modo:

  • invokeLater avvia una discussione e quella discussione pianifica un evento asincrono JLabel che è incaricato di creare la JLabel .
  • il contatore è un thread separato che dipende JLabel di JLabel in modo che possa chiamare label.setText("You have " + i + " seconds.");

Ora hai una race condition: JLabel deve essere creata PRIMA che il counter thread si avvii, se non viene creato prima setText del counter thread, il tuo thread counter chiamerà setText su un object non inizializzato.

Per garantire che la condizione di gara venga eliminata, dobbiamo garantire l’ordine di esecuzione e un modo per garantire che sia eseguire showGUI() e counter.start() sequenza sullo stesso thread:

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

Ora showGUI(); e counter.start(); vengono eseguiti dallo stesso thread, quindi la JLabel verrà creata prima JLabel del counter .

Aggiornare:

D: E non capisco cosa ci sia di speciale in questo thread.
A: Il codice di gestione degli eventi Swing viene eseguito su un thread speciale noto come thread di invio degli eventi. La maggior parte del codice che richiama i metodi Swing viene eseguito anche su questo thread. Ciò è necessario perché la maggior parte dei metodi degli oggetti Swing non sono “thread-safe”: il loro richiamo da più thread rischia di interferire con i thread o errori di coerenza della memoria. 1

D: Quindi, se abbiamo una GUI, dovremmo avviarla in un thread separato?
A: Probabilmente c’è una risposta migliore della mia, ma se vuoi aggiornare la GUI dall’EDT (cosa che fai), devi avviarla dall’EDT.

D: E perché non possiamo iniziare il thread come qualsiasi altro thread?
A: Vedi la risposta precedente.

D: Perché usiamo alcuni invokeLater e perché questo thread (EDT) inizia ad eseguire la richiesta quando è pronta. Perché non è sempre pronto?
A: L’EDT potrebbe avere altri eventi AWT che deve elaborare. invokeLater Fa in modo che doRun.run () sia eseguito in modo asincrono nel thread di distribuzione dell’evento AWT. Ciò accadrà dopo che tutti gli eventi AWT in sospeso sono stati elaborati. Questo metodo deve essere utilizzato quando un thread dell’applicazione deve aggiornare la GUI. 2

Stai effettivamente avviando il counter thread dall’EDT. Se si chiama counter.start() dopo il blocco invokeLater , è probabile che il contatore inizi a essere eseguito prima che la GUI diventi visibile. Ora, poiché stai costruendo la GUI nell’EDT, la GUI non esisterebbe quando il counter inizia ad aggiornarlo. Fortunatamente sembra che tu stia inoltrando gli aggiornamenti della GUI all’EDT, che è corretto, e dal momento che EventQueue è una coda, il primo aggiornamento avverrà dopo che la GUI è stata costruita, quindi non ci dovrebbe essere alcun motivo per cui ciò non funzionerebbe. Ma che senso ha aggiornare una GUI che potrebbe non essere ancora visibile?

Cos’è l’EDT?

È una soluzione hacky per i numerosi problemi di concorrenza che l’API Swing ha;)

Seriamente, molti componenti di Swing non sono “thread safe” (alcuni famosi programmatori sono arrivati ​​a chiamare Swing “thread ostile”). Avendo un thread univoco in cui tutti gli aggiornamenti vengono apportati a questi componenti thread-ostili, stai evitando molti potenziali problemi di concorrenza. In aggiunta a ciò, è anche garantito che esegua il Runnable che lo passi attraverso invokeLater in un ordine sequenziale.

Quindi alcuni pignoli:

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

E poi:

Nel metodo main iniziamo anche il contatore e il contatore (per costruzione) viene eseguito in un altro thread (quindi non si trova nel thread di invio dell’evento). Destra?

Non si avvia realmente il contatore nel metodo principale. Si avvia il contatore nel metodo run () del Runnable anonimo che viene eseguito sull’EDT. Quindi inizi davvero il Thread counter dall’EDT, non il metodo principale. Quindi, poiché è un thread separato, non viene eseguito sull’EDT. Ma il contatore è sicuramente avviato sull’EDT, non nel Thread esegue il metodo main(...) .

È pazzesco ma ancora importante visto la domanda che penso.

Questo è semplice, è il seguente

Passo 1 . Il thread iniziale chiamato anche thread principale viene creato.

Passaggio 2. Creare un object eseguibile e passarlo a invokeLate ().

Passaggio 3. Inizializza la GUI ma non crea la GUI.

Passaggio 4. InvokeLater () pianifica l’object creato per l’esecuzione su EDT.

Passaggio 5. La GUI è stata creata.

Passaggio 6. Tutti gli eventi che si verificano verranno inseriti in EDT.