GUI in Threads/ExecutorService

Halli Hallo

Eine Frage zum zeichnen von GUI Elementen innerhalb von Threads. Ich habe einen Knopf welcher die Anwendung schließt. Doch bevor das geschieht sollen verschiedene Objekte gefragt werden ob davor noch etwas zu erledigen ist. Sind alle Aufgaben erfüllt, soll wirklich der schließen-Befehl abgesetzt werden.

Mein Plan war nun ein Event zu erzeugen und an den entsprechenden Stellen abgefangen wird. Dort wird dann ein Thread erzeugt und einem ExecutorService übergeben, der die Aufgaben abareitet. Falls irgendwas schief geht und zu lange dauert besitzt dieser ein “awaitTermination”.
Mein Problem ist nun, das die GUI nur halbherzig aktualisiert wird. Genau genommen solange wie es eine “awaitTermination” gibt. Gibt es sie nicht dann läuft alles i.O. aber in der Theorie kann der Executor ja unendlich laufen.
Was ist hier die Lösung? Bisher sieht es in etwa so aus

class Main{

public void close(){
MyExecutor.create();
closeApp();
}
}


class MyExecutor{
public static void create(){
service = Executors.newSingleThreadedExecutor();

fireEvent(WILL_CLOSE);

closeService();
}

public static submit(Thread task){
services.submit(task);
}

private static void closeService(){
service.shutdown();
service.awaitTermination(1, TimeUnit.MINUTES);
}
}

class MyListener{

handleEvent(Event event){
Thread t = new Thread(){
run(){
JOptionPane.showDialog.....
}
}
MyExecutor.submit(t);
}
}

Das Dialogfenster wird zwar gezeichnet, ist aber halb transparent:

alles ziemlich unklar,

die Aktion beim Schließen-Button sollte auf jeden Fall in einem separaten Thread,
solange die Aktion läuft, wozu auch evtl. Warten gehören kann, solange wohl vom AWT-Thread ausgeführt, sind Zeichenprobleme nicht überraschend

MyListener klingt gar nicht schlecht in der Hinsicht, aber was ist die close()-Methode, wer wo wie ruft die auf?
was ist mit closeApp() bzw. wohl eher interesant closeService(), warum und wie ist das verzögert (edit: ok, awaitTermination) und welcher Thread wartet dabei?

ein vollständiges Mini-Testprogramm wäre besser


einem ExecutorService keine Thread-Objekte übergeben, ein einfaches Runnable reicht dann,
weniger Gefahren von simpler Verwechslung bis gar eigenständigen Lauf dieses Thread-Objektes

Ich hab mal versucht das ganze etwas kleiner nachzubauen. Ein Dialog welcher einen Button hat zum schließen. Bevor das geschieht sollen aber mögliche Operationen noch ausgeführt werden.

also wie ich bereits vermutete, close() wird vom AWT-Thread ausgeführt, dieser ist solange blockiert

jede längerfristige Aktion eines ActionListeners usw. muss in einem Thread passieren,
der Listener sofort zu Ende sein, damit der AWT-Thread wieder anderes machen kann, z.B. Zeichnen, neue Events verarbeiten,
einfache Regel

                public void actionPerformed(ActionEvent arg0)
                {
                    Runnable r = new Runnable()
                        {
                            @Override
                            public void run()
                            {
                                MyExecutor.create();
                                dia.dispose();
                            }
                        };
                    new Thread(r).start();
                }

wäre eine Dummy-Variante unter Ignorierung allen anderen, geht

alles mit dem ExecutorService dann wohl obsolet,
andersrum aber wohl kaum machbar, es kann nicht im ExecutorService selber auf dessen Ende gewartet werden…,
um einen echten Thread kommst du nicht so leicht herum


es gibt allerdings auch modale Dialoge,
https://docs.oracle.com/javase/tutorial/uiswing/misc/modality.html

                public void actionPerformed(ActionEvent arg0)
                {
                    int result = JOptionPane.showConfirmDialog(null, "Hello World");
                    System.out.println("Name: "+this+"_"+result);
                }

zeigt einen funktionierenden Dialog an und der ActionListener hier ist bis zum Dialog-Ende unterbrochen, also wartend,
ohne dass der AWT-Thread blockiert ist

aber entspricht wohl nicht deiner Plugin-Struktur mit wer weiß was noch allem im Hintergrund,

edit:
vielleicht ist aber eine gewisse Blockade akzeptabel, solange nur die Dialoge funktionieren?
warum funktionierte eigentlich deiner nicht, ich schaue nochmal :wink:

edit: weil der Dialog von separaten Thread aufgerufen wurde, gut gemeint, aber das geht nicht wenn AWT-Thread blockiert ist,
während vom blockieren AWT-Thread selber möglich wegen modal…, schafft sich dort selber den Freiraum zu zeichnen

Also im Grundkonstrukt ist es eine Anwendung welche mit verschiedenen Erweiterungen daherkommen kann. Nun soll, wenn der schließen-Button gedrückt wird dieser Prozess unterbrochen werden und ein Event abgesetzt werden. Eine Möglichkeit wäre das ein PlugIn eine SMS schickt einem Status schickt. Ist diese SMS versendet soll das schließen ganz normal weitergehen. Zwingend ein Dialog muss also nicht vorhanden sein, wäre aber möglich und war die erste/einfachste Möglihckeit mit der ich getestet habe.

Wenn das so wie gedacht nicht geht, was wäre denn ein andere Option um das Szenario umzusetzen?

zwei Varianten habe ich doch schon genannt, ich möchte nicht zu viel wiederholen:

  • wenn alles der AWT-Thread macht, kein ExecutorService, dann kann dieser modale Dialoge fehlerfrei anzuzeigen,
    in sonstiger Zeit blockierende GUI, aber das muss nicht schlimm sein

  • ansonsten generell am Anfang separaten Thread starten,
    ob der dann alles macht oder auf ExecutorService wartet ist ziemlich gleichgültig für das Problem

Dann schauich mal nach Variante zwie, denn es ist nicht fix das immer Dialoge/GUI Elemente angezeigt werden.

Nochmal zu deinem Beispiel oben mit dem Dummy. Warum wäre das mit dem Service dann obsolet? Aktuell wird der ja dazu genutzt die angefallenen Aufgaben nacheinander abzuarbeiten und nach einer gewissen Zeit selbst abzubrechen damit es weiter gehen kann. Wenn ich das nun ohne diesen löse werden auf Aufgaben erledigt, aber der Dialog geht vorher schon zu.
Oder meintest du stattdessen eine eigene Lösung um zu warten?

die Aufgaben müssen bearbeitet werden, von irgendeinem Thread,
wenn kein ExecutorService beteiligt ist, könnte der in der actionPerformed erzeugte Thread X alles machen,

was er (oder vorher der AWT-Thread) sowieso schon macht ist die statische Methode in MyExecutor, sowie der Aufruf von handleEvent() in den Plugins,
damit auch die Ausführung dieser handleEvent()-Methoden,

dort erzeugst du im Moment ein Runnable für den ExecutorService,
stattdessen könnten dort die Aktionen einfach als normaler Code stehen -> von Thread X abgearbeitet,
der Natur nach alle Plugins nacheinander


ExecutorService beibehalten, aus welchen Gründen auch immer, und X auf dessen Ende warten zu lassen,
ist auch eine der denkbaren Variante

Ah, jetzt hats “Klick” gemacht.
Im Endeffekt gibt es also nicht für jede Aufgabe einen eigenen Thread sondern einen übergeordneten der die ganze “Schließen”-Aktion handelt. Und dadurch ist auch kein manuelles warten mehr nötig da der Code eh sequentiell bearbeitet wird.