2010-03-20 8 views
12

Mit Hilfe der Leute auf stackoverflow konnte ich den folgenden Arbeitscode eines einfachen GUI Countdowns erhalten (der nur ein Fenster anzeigt, das Sekunden herunter zählt). Mein Hauptproblem mit diesem Code ist das invokeLater Zeug.Wie funktioniert der Ereignisversand-Thread?

Soweit ich es verstehe invokeLater, sendet es eine Aufgabe an den Event-Dispatching-Thread (EDT) und dann führt der EDT diese Aufgabe aus, wann immer er "kann" (was auch immer das bedeutet). Ist das richtig?

Zu meinem Verständnis, funktioniert der Code wie folgt aus:

  1. Im main Methode, die wir verwenden invokeLater das Fenster (showGUI-Methode) zu zeigen. Mit anderen Worten, der Code, der das Fenster anzeigt, wird in der EDT ausgeführt.

  2. Im main Methode Wir beginnen auch die counter und die Zähler (durch Konstruktion) wird in einem anderen Thread ausgeführt (es ist also nicht in dem Gewinde Ereignis Dispatching). Recht?

  3. Die counter wird in einem separaten Thread ausgeführt und ruft in regelmäßigen Abständen updateGUI auf. updateGUI soll die GUI aktualisieren. Und die GUI arbeitet im EDT. Daher sollte updateGUI auch im EDT ausgeführt werden. Aus diesem Grund ist der Code für die updateGUI in invokeLater beigefügt. Ist das richtig?

Was mir nicht klar ist, warum wir die counter vom EDT rufen. Jedenfalls wird es im EDT nicht ausgeführt. Es startet sofort ein neuer Thread und die counter wird dort ausgeführt. Also, Warum können wir nicht die counter in der Hauptmethode nach dem invokeLater Block anrufen?

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 hier ist eine ausführlichere Diskussion: http://StackOverflow.com/Questions/182316/java-swing-Libraries-Thread-Safety – Kiril

+0

@Roman * Hinweis: * Ihr Counter tut ** nicht ** starten auf der EDT beginnt es am Hauptthread. Der Zähler aktualisiert die GUI durch die updateGUI-Methode, die die Aktualisierungen auf dem EDT durchführt (wegen des Aufrufs von invokeLater). – Kiril

Antwort

16

Wenn ich verstehe Ihre Frage richtig sind Sie fragen, warum Sie dies nicht tun können:

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

Der Grund, warum Sie nicht tun können, ist es, weil der Planer keine Garantie macht ... nur weil Sie aufgerufen showGUI() und dann aufgerufen counter.start() bedeutet nicht, dass der Code in showGUI() vor dem Code in der Lauf-Methode der counter ausgeführt wird.

Betrachten Sie es auf diese Weise:

  • invokeLater beginnt ein Gewinde und das Gewinde ist Pläne ein asynchrones Ereignis auf der EDT, die mit der Erstellung die JLabel beauftragt ist.
  • der Zähler ist ein separater Thread, der auf dem JLabel hängt existiert, so dass es label.setText("You have " + i + " seconds.");

Jetzt haben Sie eine Race-Bedingung nennen kann:JLabel muss, bevor der counter Thread startet erstellt werden, wenn es nicht erstellt wird Bevor der Counter-Thread gestartet wird, ruft Ihr Counter-Thread setText für ein nicht initialisiertes Objekt auf.

Um sicherzustellen, dass die Race-Bedingung beseitigt wir die Reihenfolge der Ausführung und eine Art und Weise garantieren muss gewährleistet ist showGUI() und counter.start() nacheinander auf dem gleichen Thread auszuführen:

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

Jetzt showGUI(); und counter.start(); werden vom selben Thread ausgeführt, daher wird JLabel erstellt, bevor counter gestartet wird.

Update:

F:Und ich verstehe nicht, was über diesen Thread Besonderes ist.
A: Swing-Ereignisbehandlungscode wird in einem speziellen Thread ausgeführt, der als Ereignisversand-Thread bezeichnet wird. Der meiste Code, der Swing-Methoden aufruft, läuft auch auf diesem Thread. Dies ist erforderlich, da die meisten Swing-Objektmethoden nicht "threadsicher" sind: Wenn sie aus mehreren Threads aufgerufen werden, besteht die Gefahr von Threadinterferenzen oder Speicherkonsistenzfehlern. 1

Q:Wenn wir also eine GUI haben, warum sollten wir beginnen, es in einem separaten Thread?
A: Es gibt wahrscheinlich eine bessere Antwort als meine, aber wenn Sie die GUI von der EDT aktualisieren möchten (was Sie tun), dann müssen Sie es von der EDT starten.

Q:Und warum können wir nicht einfach den Thread wie jeder andere Thread starten?
A: Siehe vorherige Antwort.

F:Warum wir einige invokeLater verwenden und warum dieser Thread (EDT) beginnt, die Anfrage auszuführen, wenn sie fertig ist. Warum ist es nicht immer bereit?
A: Der EDT kann einige andere AWT-Ereignisse haben, die er verarbeiten muss. invokeLater Verursacht die Ausführung von doRun.run() asynchron auf dem AWT-Event-Dispatching-Thread.Dies geschieht, nachdem alle ausstehenden AWT-Ereignisse verarbeitet wurden. Diese Methode sollte verwendet werden, wenn ein Anwendungsthread die GUI aktualisieren muss. 2

+1

invokeLater startet keinen neuen Thread. Stattdessen plant es die Ausführung von Runnable im vorhandenen AWT-Ereignisversand-Thread. –

+0

@Steve danke, korrigiere ich die Zeile. Wenn du im letzten Abschnitt der Q/AI bemerkst, kopierst du die Dokumentation und es heißt konkret: * "[invokeLater causes] doRun.run() wird asynchron auf dem AWT Event dispatching thread ausgeführt." * Meine Einschätzung der Race Condition ist so oder so. – Kiril

+0

Es gibt keinen Grund, warum Sie invokeAndWait hier nicht verwenden könnten, oder? Das würde den aktuellen Thread warten lassen, bis das Runnable ausgeführt wurde. –

2

Sie sind eigentlich Start der counter Thread aus dem EDT. Wenn Sie counter.start() nach dem invokeLater-Block aufgerufen haben, würde der Zähler wahrscheinlich starten, bevor die GUI sichtbar wird. Jetzt, da Sie die GUI im EDT konstruieren, würde die GUI nicht existieren, wenn die counter beginnt, sie zu aktualisieren. Glücklicherweise scheinen Sie die GUI-Aktualisierungen an den EDT weiterzuleiten, was korrekt ist, und da die EventQueue eine Warteschlange ist, wird die erste Aktualisierung nach der Erstellung der GUI stattfinden, also sollte es keinen Grund geben, warum dies nicht funktionieren würde. Aber was ist der Sinn der Aktualisierung einer GUI, die vielleicht noch nicht sichtbar ist?

+0

Danke. Was du sagst, ergibt für mich Sinn.Also, wenn ich Counter nach dem InvokeLater nach einer gewissen Verzögerung starte, sollte es funktionieren? – Roman

+0

Eigentlich habe ich in der ersten Version meiner Antwort einen Fehler gemacht. Ich denke, es sollte in beide Richtungen funktionieren. –

+0

@Roman Nein, es würde immer noch nicht funktionieren ... eine Zeitverzögerung gibt dir keine * Garantie *, es kauft dir nur Zeit und du hast immer noch die Race Condition. Du willst deine Race Conditions nicht maskieren, du willst sie * eliminieren *. – Kiril

1

Was ist der EDT?

Es ist eine Hacky Abhilfe Um das sehr viele Concurrency Probleme, dass der Swing-API hat;)

Im Ernst, sind viele Swing-Komponenten nicht „thread-safe“ (einige berühmte Programmierer gingen so weit wie Swing-Aufruf "Faden feindlich"). Durch die Verwendung eines einzigartigen Threads, bei dem alle Updates für diese thread-feindlichen Komponenten vorgenommen werden, vermeiden Sie eine Vielzahl möglicher Nebenläufigkeitsprobleme. Darüber hinaus wird Ihnen auch garantiert, dass die Runnable, die Sie durchlaufen, unter Verwendung von invokeLater in einer sequenziellen Reihenfolge ausgeführt wird.

Dann einige Erbsenzählerei:

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

Und dann:

Im Hauptverfahren beginnen wir auch die Zähler und den Zähler (durch Bau) in einem anderen Thread ausgeführt wird (so Es ist nicht im Fall Dispatching Thread). Recht?

Sie starten den Zähler nicht wirklich in der Hauptmethode. Sie starten den Zähler in der run() Methode des anonymen Runnable, die auf dem EDT ausgeführt wird. Also Sie wirklich Start der Zähler Thread aus dem EDT, nicht die Hauptmethode. Dann, weil es ein separater Thread ist, ist es nicht auf dem EDT ausgeführt. Aber der Zähler definitiv ist gestartet auf dem EDT, nicht in der Thread Durchführung der main(...) Methode.

Es ist pingelig, aber immer noch wichtig gesehen die Frage, denke ich.

+0

WizardOfOdds, ich verstehe, dass Zähler nicht auf dem EDT ausgeführt wird. Es wird in einem separaten Thread ausgeführt, der vom EDT (Thread) gestartet wird. Ich verstehe auch, dass der Zähler nicht im selben Thread wie die Hauptmethode ausgeführt wird. Ich wollte nur sagen, dass die Hauptmethode 'showGUI' und' counter.start' an den EDT gesendet hat und 'counter.start' startet einen neuen Thread von EDT. – Roman

+0

@Roman: yup, genau ... Aber es ist wichtig, den Wortlaut richtig zu machen, falls jemand anderes diese Frage/Antworten liest :) – SyntaxT3rr0r

0

Dies ist einfach, es als

Schritt 1

folgt. Der anfängliche Thread, der auch als Hauptthread bezeichnet wird, wird erstellt.

Schritt 2. Erstellen Sie ein ausführbares Objekt und übergeben Sie es an invokeLate().

Schritt 3. Dies initialisiert die GUI, aber erstellt nicht die GUI.

Schritt 4. InvokeLater() plant das erstellte Objekt zur Ausführung auf EDT.

Schritt 5. Die GUI wurde erstellt.

Schritt 6. Alle auftretenden Ereignisse werden in EDT platziert.