Swing und der Event-Dispatch-Thread

Ich sehe im Forum Beispiele, die die Erzeugung der Oberfläche in

            @Override
            public void run() {
                createAndStartGui();
            }
        });```
hüllen. Dann wird die Erstellung der grafischen Oberfläche und das `.setVisible(true)` im EDT durchgeführt.

Ein Programm läuft aber auch genauso ohne `invokeLater` (an dieser Stelle). Gibt es da irgendwelche automatischen Mechaniken von Swing? Kann es passieren, dass ein Oberflächenstart ohne `invokeLater`irgendwann einmal misslingt?



Eine andere Frage: Wenn aus einem anderen Thread heraus, wie etwa einer Game-Loop, Dialoge mit dem Benutzer eröffnet werden sollen, müssen diese ja über den EDT erfolgen. Möchte man auf eine Antwort warten, brauch man also
```    private String answer;
        [..]
    public String askUserAboutXYZ() {
        answer = null;
        try {
            EventQueue.invokeAndWait(new Runnable() {
                @Override
                public void run() {
                    answer = gui.askUserAboutXYZ();
                }
            });
        }
        catch (InvocationTargetException | InterruptedException e) {
            e.printStackTrace();
            answer = null;
        }

        return answer;
    }```
oder soetwas. Es "funktioniert" in Tests allerdings auch ein einfaches `return gui.askUserAboutXYZ();`. Wie zuverlässig dies nun funktioniert, ist eine Frage. Die andere, wodurch die `InvocationTargetException` oder die `InterruptedException` ausgelöst werden könnten.

Generell ist mir die Mechanik unklar, wann und wie Swing Sachen im EDT ausführt und wann nicht, und warum Dinge "funktionieren", wenn man den EDT ignoriert und sie einfach direkt aus dem Main-Thread oder einem anderen Thread, der nicht der EDT ist, aufruft.

Zum ersten: Ja, es kann passieren, dass der GUI-Aufbau mißlingt, wenn er nicht vom EDT gemacht wird. Zugegeben, die Formulierung im Original-Artikel (der jetzt leider nicht mehr auffindbar ist) dazu war IMHO nicht ausreichend

Once a Swing component has been realized, all code that might affect or depend on the state of that component should be executed in the event-dispatching thread.

Das Wort „realized“ wurde zwar noch definiert, bezog sich aber dort NICHT auf das reine Erstellen des GUIs. Sollte es aber. Ganz konkret kann ich zumindest sagen, dass ich schonmal über ein paar Ecken mit einem (offenbar SEHR speziellen) LookAndFeel zu tun hatte, das schlicht mit einer Exception abgekachelt ist, wenn eine GUI-Component NICHT auf dem EDT erstellt wurde. Aber auch bei den konventionelleren LaFs gibt es vermutlich keine Garantie, dass alles immer funktioniert, wenn man nicht den EDT verwendet (auch wenn ich selbst DA noch nie irgendwelche Probleme beobachtet habe…)

Zum zweiten: Da müßte man genauer wissen, was in „askUserAboutXYZ“ gemacht wird: Ich vermute, das ist ein JOptionPane oder irgendeine andere Art von Modalem Dialog?

[QUOTE=Crian]
Generell ist mir die Mechanik unklar, wann und wie Swing Sachen im EDT ausführt und wann nicht, und warum Dinge “funktionieren”, wenn man den EDT ignoriert und sie einfach direkt aus dem Main-Thread oder einem anderen Thread, der nicht der EDT ist, aufruft.[/QUOTE]

Teile von Swing sind leider nicht Threadsicher. Daher sollten Operationen, die eine Modifikation von Swing-Komponenten zur Folge haben im Event-Dispatch-Thread ausgeführt werden. Andernfalls kann es zu nicht nachvollziehbarem Verhalten der GUI kommen. Die Fehlersuche wird dann sehr unschön.

hier einige Infos von Oracle zum EDT:
http://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html

@Marco13 : Danke, das ist sehr aufschlussreich. Es kann also schief gehen, und es ist sauberer, alles dem EDT zu überlassen. So hatte ich es mir gedacht, wenn auch noch nicht in allen Prijekten so verwendet.

Genau.
@HLX : Das ist mir bewusst, und da hatte ich auch schon mal seltsames Verhalten, dass sich durch ein invokeLater „beheben“ ließ. Ich möchte es nur halt nicht einstreuen, wenn irgendwas komisch ist, sondern das Prinzip verstehen und dann auch überall konsequent anwenden. Daher auch die Frage, ob es schon bei der Erstellung der Oberfläche „nötig“ ist. Und der Thread hat dies schon mit „ja“ beantwortet.

Zum zweiten: Da muss ich zugeben, das ich eben auch kurz im Web nach einer möglichst offiziellen, verbindlichen Antwort gesucht habe. Bei AWT war es noch üblich, die show()/setVisible(true) eines MODALEN Dialogs direkt von einem anderen Thread aufrufen zu können (genaugenommen steht in der Doku explizit dabei, dass man sie AUCH vom EDT aus aufrufen DARF!). Aber ich schätze, bei Swing ist das anders: Um die Single Thread Rule einzuhalten muss man wohl invokeAndWait verwenden (und ich kann im Moment nicht ausschließen, dass ich schon Code geschrieben habe, wo es auch ohne invokeAndWait aufgerufen wurde :o ). Wie das bei Threading-Fragen so ist: „Das ist zwar nicht richtig so, aber funktoniert … … … meistens“ :wink:

Die InterruptedException wird geworfen, wenn auf dem wartenden Thread „interrupt“ aufgerufen wird. Die InvocationTargetException afair dann, wenn das Runnable eine (Unchecked) Excepton wirft.

Ich philosophiere ein wenig vor mich hin, bitte korrigiert mich, wenn ich falsch liege. Denn ich hatte von Threads bis vor kurzem ein falsches Bild (Klassengrenzen entsprechen Threadgrenzen) und habe dies gerade erst richtig gestellt (bekommen - danke nochmal).

Wenn ich in einer Logik (Controller) eine Methode habe wie

        // Verändere Daten (des Modells) entsprechend
        String input = askUserAboutXYZ();
        if (input != null) {
            // tu was sinnvolles mit der Benutzereingabe
        }
    }``` mit der im ersten Post schon genannten, hypothetischen
```    public String askUserAboutXYZ() {
        answer = null;
        try {
            EventQueue.invokeAndWait(new Runnable() {
                @Override
                public void run() {
                    answer = gui.askUserAboutXYZ(); // Methode fragt in der view (EDT) nach einer Eingabe per Dialog o.ä.
                }
            });
        }
        catch (InvocationTargetException | InterruptedException e) {
            e.printStackTrace();
            answer = null;
        }

        return answer;
    }```
wobei die erste Methode, `ereignisXEingetreten()` aus der Gui heraus aufgerufen wurde (als Reaktion auf Klick auf einen Button oder sonstetwas), dann wird es knallen, weil der Aufruf ja aus dem EDT erfolgt und ein `invokeAndWait` nach dem, was ich gelesen habe, nicht aus dem EDT selbst aufgerufen werden darf, und ja auch völlig überflüssig ist, wenn man sich schon dort befindet.

Ich muss mir also sehr genau klar sein darüber, welcher Thread welche Methoden benutzt (und sie tun das über Klassengrenzen hinweg).

Wollte ich also, dass ich `askUserAboutXYZ` sowohl aus dem EDT (auf Benutzereingabe), als auch von anderer Seite, etwa aus einer Game-Loop oder sonstigem heraus, aufrufen kann, müsste ich dort unterscheiden, in welchem Thread ich bin oder zwei Methoden anbieten, eine aus dem EDT und eine für andere Threads.

Das Beispiel ist jetzt ein wenig konstruiert, ich weiß gar nicht, ob so etwas bei meinen Projekten vorkommt.

*** Edit ***

[QUOTE=Marco13]Wie das bei Threading-Fragen so ist: "Das ist zwar nicht richtig so, aber funktoniert ... ... .... meistens" ;) [/QUOTE]

Und das ist so ärgerlich. Wie sonst hätte ich mit einem falschen Verständnis von Threads einige Anwendungen entwickeln können, die auch "funktionieren". Zum Glück hatte ich ein mulmiges Bauchgefühl und hab neulich gefragt, sonst hätte ich das wohl noch jahrelang so weiter machen können, bis es irgendwann irgendwo zu "komischen, nicht reproduzierbaren" Fehlern kommt. :eek:

Öhm also ich bin jetzt nicht ganz sicher ob du das mit den Threads verstanden hast, jedenfalls verstehe ich dein Beispiel so nicht. Normalerweise sollte es kein Problem sein, innerhalb des EDTs invokeAndWait aufzurufen, über die internen mechanismen wie die ganzen Runnables auf dem EDT abgearbeitet werden bin ich jetzt aber auch nicht im Bilde. Merk dir einfach, dass jedes set, add oder remove auf einer Swing Komponente am EDT laufen sollten, sonst gibts bugs. Wenn du Wert auf saubere Programmierung legst machst du auch jede andere Methode (z.B get) auf dem EDT). Ist ja normalerweise nicht die Welt.

Wichtig: Sämtlicher Code der von einem Listener ausgeführt wird läuft bereits auf dem EDT. Da nochmal zu invoken ist meist nicht notwendig.

Aus der API zu invokeAndWait:

This method will throw an Error if called from the event dispatcher thread.

Im konkreten Fall von invokeAndWait wäre es tatsächlich ein Problem, da die Methode überprüft ob sie im EDT läuft und ggf. eine Exception wirft. Aber die Verwendung der Methode macht ja nur Sinn wenn man gerade nicht im EDT ist - und sich dessen auch bewusst ist.
invokeLater hingegen kann man von überall - auch aus dem EDT heraus - problemlos aufrufen, da die Methode ja einfach nur die Ausführung des Runnables ans Ende der EventQueue stellt.

Die Problematik habe ich jetzt allerdings auch nicht so genau verstanden.
Für das Arbeiten mit Swing und Threads gilt einfach für Swing Methoden, die nicht Thread-safe sind und letztendlich eine Aktualisierung des UI auslösen (sollen) sicherzustellen, dass diese im EDT ausgeführt werden.

Ja, invokeAndWait aus dem EDT ist nicht erlaubt. Tatsächlich stimmt auch das:

[QUOTE=Crian]
Ich muss mir also sehr genau klar sein darüber, welcher Thread welche Methoden benutzt (und sie tun das über Klassengrenzen hinweg).

Wollte ich also, dass ich askUserAboutXYZ sowohl aus dem EDT (auf Benutzereingabe), als auch von anderer Seite, etwa aus einer Game-Loop oder sonstigem heraus, aufrufen kann, müsste ich dort unterscheiden, in welchem Thread ich bin oder zwei Methoden anbieten, eine aus dem EDT und eine für andere Threads.[/QUOTE]

Bei einigen Methoden steht es dabei, bei anderen nicht (obwohl es dabei stehen sollte), von welchem Thread sie aufgerufen werden dürfen. Man kann erstmal davon ausgehen, das ALLES, was mit dem GUI zu tun hat, NUR vom EDT gemacht werden darf. Ausnahmen sind ggf. gekennzeichnet. Und tatsächlich ist für die Fälle, wo man Threadunabhängig sein will, auch so ein Muster nicht verkehrt:

public void doSometingOnAnyThread()
{
    if (SwingUtilities.isEventDispatchThread()) doSomethingDirectly();
    else doSomethingUsingInvokeAndWait();
}

In deinem Fall könnte es aber auch gut sein, dass die Methode (vielleicht sowieso private sein kann? und) nur vom EDT aufgerufen werden können sollte, und dann eben direkt einen Modalen Dialog aufmachen kann - ohne das mit InvokeAndWait, was schon krampfig sein kann, wenn man da irgendwie einen Rückgabewert durchschleifen will…

Ja,

was schon krampfig sein kann, wenn man da irgendwie einen Rückgabewert durchschleifen will…

allerdings. Eine lokale Variable geht nicht, die müsste final sein, wenn sie final wäre, kann ich keinen Rückgabewert drin ablegen, darum ist es im ersten Post auch eine (ansonsten unnötige) Klassenvariable gewesen.

Auf dieses SwingUtilities.isEventDispatchThread() war ich auch gestoßen (in dem vom HLX verlinkten Text), aber ich hoffe erstmal, das nicht zu brauchen. Normalerweise trenne ich das genau wie du sagst, durch private / public Methoden.

Danke euch allen, ich werde den Thread mal als erledigt markieren, falls ich immer noch kein vollständig richtiges Bild von Threads im Kopf habe, oder unsicher bin, kann ich ja weiter nachfragen. schmunzelt

Die Frage die sich mir hier stellt ist doch auch, ob das invokeAndWait praktisch/nützlich ist an der Stelle.
Was, ausser ein Event in die Que des EDT zu hängen soll denn noch erreicht werden? Dann tut es doch invokeLater.
Vielleicht steh ich ja auch auf dem Schlauch aber mir wird halt die Notwendigkeit der Vorgehensweise nicht klar.

OT
[spoiler]
PS: Worann erkenne ich bei der neuen Forenoptik eigentl. den TO?[/spoiler]

Hier noch eine leichte Abwandlung davon:

public void doSometing() {
    if (!SwingUtilities.isEventDispatchThread()) {
        SwingUtilities.invokeAndWait(new Runnable() {
            public void run() {
                doSomething();
                return;
            });
        }
    }
    // hier kommt die fachliche Implementierung
}

@vanny (und auch HLX) : Das invokeAndWait braucht man in einigen speziellen Zusammenhängen, aber insbesondere dann, wenn man einen Rückgabewert hat. Da kann man nicht invokeLater verwenden, weil man ja warten muss, bis der Rückgabewert da ist. Den Rückgabewert durchschleifen kann dann frickelig sein. Es gibt zwar sowas wie “Future” u.ä. (die auch beim SwingWorker verwendet werden), aber trotzdem muss man irgendwie zwischen den Threads rumjonglieren…

Ja genau. Wenn man keine Rückgabewert braucht, dann kommt man drum herum.

Hier am Raben, allgemein noch gar nicht, fürchte ich.

Mal 'ne Frage: Weisst du ungefähr wie “repaint()” funktioniert? Ich hab’s neulich erst nachgelesen, weil da etwas war, das mir nicht einleuchtete…
Jeder Aufruf von “repaint()” queued im einen PaintEvent inkl. Zeitstempel (aber nicht jedes “repaint()” endet in einem “paint()”). Irgendwann stösst der EDT auf genau jenen, der im aktuellen Zyklus in der Queue zuerst abgelegt wurde (kann auch sein, dass nur einer abgelegt wird, dessen Zeitstempel immer aktualisiert wird, so genau hab ich’s nicht verfolgt), ruft über diesen die gewünschte “paint()”-Methode auf und ignoriert alle weiteren PaintEvents für diesen Zyklus (ist jetzt nur vereinfacht, es ist in Wirklichkeit noch um einiges komplizierter, wegen verschiedener Komponenten, die evtl. schon gezeichnet wurden usw. -> “Walk dirty Regions”).

Aber dieser Mechanismus wäre auch für dein Problem interessant… Du baust ganz einfach einen “AskUserAboutXYZEvent” und einen “AskUserAboutXYZListener”. Dann rufst von irgendwoher “askUserAboutXYZ()” auf, fragst ein Flag ab ob schon ein Event gedropped wurde und wenn nicht dropst (äh… :o) “EventQueue.postEvent()”) du halt einen. Das Flag wird dann erst im Listener (also auf jedem Fall auf dem EDT) wieder zurückgesetzt. Du brauchst dafür auch nur eine einzige Instanz des AskUserAboutXYZEvents, pro Zyklus brauchst du dann nur Zeitstempel und “dropped”-Flag korrigieren.

edit: Das mit der EventQueue muss 'ne Verwechslung mit irgendwas eigenem sein, da kommt man auf den ersten Blich irgendwie gar nicht dran. Naja… ersetzen wir Listener durch Runnable und Event gegen “invokeLater()”, schon kommts aufs gleiche raus.

Ja, so ungefähr wusste ich das auch (dass es nicht jedesmal ausgeführt wird und dafür auf irgendeinem Stapel abgelegt und später abgesammelt wird).

Ganz im Ernst hatte ich auch schon darüber nachgedacht, das asynchron zu machen. Genau wie ich Tastatureingaben des Benutzers (die natürlich aus dem EDT stammen) im Moment auf ähnliche Weise in einer Thread-saven Queue sammle und dann, wenn die Game-Loop mal wieder aktiv wird, auswerte.