SWT Eventdispatcher

Bernd Hohmann

Top Contributor
Irgendwie stehe ich mit dem SWT-Eventdispatching auf Kriegsfuss.

Im Moment ist das so, dass das Hauptfenster den Eventdispatcher hat, die Subfenster sind Dialoge (wird alles im WindowBuilder gebaut). Daher hat auch jedes Fenster eine Dispatcherschleife.

Das hat mich bislang nicht weiter gestört bis ich festgestellt habe, dass asyncExec den Kram nicht wirklich asynchron zum ED ausführt.

Also zb. im Hauptfenster "asyncExec(new Runnable() ... loadFiles())"; und im loadFiles wird dann ein Fenster aufgemacht mit dem Effekt, dass die Ausführung von "loadFiles" solange angehalten wird bis das Fenster wieder zu ist.

Jetzt meine 2 Fragen:

1) Wie managed man es, dass die Subfenster (normalerweise Dialoge) ohne eigenen ED auskommen?

2) Wie bekommt man eine echte asynchrone Ausführung von Shells/Dialogen hin?

Bernd
 

Bernd Hohmann

Top Contributor
Also bisserl weiter bin ich jetzt schon beim Verständnis: Das Mouse/Keyevent wird direkt aus der ED-Schleife abgezweigt (dh. der Event-Dispatcher "hängt" an dieser Stelle). Wenn ich jetzt an dieser Stelle ein weiteres Fenster aufmache, muss dort entweder auch eine ED-Schleife gemacht werden (Fenster bleibt damit offen und die gesamte App reagiert wieder) oder ich muss direkt wieder rausspringen um im Main-ED weiterarbeiten zu können.

Eine Konstruktion wie ich ohne separate ED-Loop in meinem Subfenster warten kann dass es wieder geschlossen wird (zb. wenn das Fenster irgendein Ergebnis zurückliefern soll) hab ich nicht gefunden.

Mift...
 

Marco13

Top Contributor
Ich habe von SWT ziemlich genau 0 Ahnung (naja, 0.07331 vielleicht), aber ... suchst du das, was in Swing ein Modaler Dialog ist? ???:L
 

Bernd Hohmann

Top Contributor
Danke für die Unterstützung, aber ein Dialog der einfach nur dumm warten soll braucht einen eigenen ED (steht auch so im Javadoc).

Das mit dem "manuellen" ED scheint irgendwie zum Design von SWT zu gehören. Hat mich bislang nicht gestört weil ich einfach überall wo die GUI nicht reagierte eine ED-Schleife reingeknallt habe - diese Vorgehensweise erscheint mir jetzt aber etwas seltsam.

Ich denke, dass ich mein eigentliches Problem nun gefunden habe... und kaum baue ich das kleinste mögliche Beispiel läuft es darin.

Problem wird vertagt bis die neue Küche eingebaut ist, zu viele Baustellen im Moment :)

Bernd
 

Sonecc

Gesperrter Benutzer
Also grundsätzlich gesehen, kannst du JFace Dialoge verwenden, die dir die Arbeit mit dem ED abnehmen.

Ansonsten, wenn die GUI nicht reagiert, ist es ein Design Problem. Eine ED-Schleife einbauen ist dann alles andere als ok. Wie immer sollte gelten, dass lange dauernde Aktionen nicht im UIThread laufen dürfen. Reagiert die GUI aber nicht mehr, ist genau das der Fall
 

Bernd Hohmann

Top Contributor
Also grundsätzlich gesehen, kannst du JFace Dialoge verwenden, die dir die Arbeit mit dem ED abnehmen.

Ich hab ja nicht nur das Problem mit dem Dialog sondern generell keine Idee, wie man längerlaufende Aktionen entkoppelt wenn die Aktion im ED gestartet wird.

Eigentlich sollte asyncExec genau das erledigen, macht es aber nicht (jedenfalls nicht in Eclipse): Ich falle sofort aus asyncExec heraus (das ist korrekt), aber die ED-Schleife interessiert sich nicht die Bohne dass da fleissig die UI aktualisiert wird (selbst display.wake() hilft nicht). Es stehen aber definitiv Events an, denn wenn ich in sample_method() das readAndDispatch() aufrufe wird auch die UI aktualisiert.

Ich hab hier irgendwo einen kapitalen Denkfehler.

Java:
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.*;

public class ED_Test {
	protected Display display;
	protected Label lblHelp;
	protected Label lblCounter;
	protected Shell shell;

	public static void main(String[] args) {
		try {
			ED_Test window = new ED_Test();
			window.open();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Open the window.
	 */
	public void open() {
		display = Display.getDefault();
		shell = new Shell();
		shell.setSize(319, 99);
		shell.setText("SWT Application");
		// ###
		lblHelp = new Label(shell, SWT.NONE);
		lblHelp.setBounds(10, 10, 291, 17);
		lblHelp.setText("Haue 'g' für weiter / 'f' für Dateidialog");
		// ###
		lblCounter = new Label(shell, SWT.NONE);
		lblCounter.setBounds(10, 44, 72, 17);
		lblCounter.setText("Counter");

		display.addFilter(SWT.KeyDown, new Listener() {
			public void handleEvent(Event e) {
				keyhandler(e);
			}
		});
		shell.open();
		shell.layout();
		while (!shell.isDisposed()) {
			if (!display.readAndDispatch()) {
				System.out.println("dispatched " + System.currentTimeMillis());
				display.sleep();
			}
		}
	}

	private void keyhandler(Event e) {

		System.out.println("Keyhandler " + e.toString());

		//if (e.keyCode == 'g') sample_method();
		if (e.keyCode == 'f') load_file();

		if (e.keyCode == 'g') {
			display.asyncExec(new Runnable() {
				public void run() {
					sample_method();
				}
			});
			System.out.println("raus aus asyncexec");
		}
	}

	private void sample_method() {
		lblCounter.setText("Anfang");
		for (int i = 0; i < 10; i++) {
			lblCounter.setText(i + "");
			System.out.println(i);
			//while (display.readAndDispatch()) {}
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e1) {}
		}
		lblCounter.setText("Ende");
	}

	private void load_file() {
		FileDialog fd = new FileDialog(shell);
		fd.open();
	}
}

Bernd
 
G

Gast2

Gast
Nimm doch die Job API
On the Job: The Eclipse 3.0 Jobs API
Eclipse Jobs and Background Processing

Außerdem weißt du schon was das async macht?
Das async wird ebenfalls im UIThread ausgeführt und deshalb kann er auch nicht neuzeichnen wenn du diesen jedes mal schlafen legst und darum werden solange auch keine anderen Events ausgeführt usw.

Ich hab ja nicht nur das Problem mit dem Dialog sondern generell keine Idee, wie man längerlaufende Aktionen entkoppelt wenn die Aktion im ED gestartet wird.
Mit eigenen Threads wie in Swing auch oder eben die Job API...

Eigentlich sollte asyncExec genau das erledigen, macht es aber nicht (jedenfalls nicht in Eclipse): Ich falle sofort aus asyncExec heraus (das ist korrekt), aber die ED-Schleife interessiert sich nicht die Bohne dass da fleissig die UI aktualisiert wird (selbst display.wake() hilft nicht). Es stehen aber definitiv Events an, denn wenn ich in sample_method() das readAndDispatch() aufrufe wird auch die UI aktualisiert.
Nein das soll das asyncExec ganz sicher nicht erledigen, les mal die JavaDoc dazu, was async genau macht. Das asyncExec startet KEINEN Background Prozess (das brauchst du aber!!!). Weil wenn du in einem Background Prozess bist kannst auf keine UI Elemente mehr zugreifen, ansonten würde eine Exception fliegen...
 
Zuletzt bearbeitet von einem Moderator:
G

Gast2

Gast
Ich vermute mal du willst sowas in der Art, dein Code einfach abgerändert... Jetzt kannst mehrmals keys absetzen und du hast konkurrienden Zugriff auf das label usw. Aber mit Threads lagert man länger laufende Aktion aus...

Java:
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;

public class ED_Test
    {
    protected Display display;
    protected Label   lblHelp;
    protected Label   lblCounter;
    protected Shell   shell;

    public static void main(String[] args)
        {
        try
            {
            ED_Test window = new ED_Test();
            window.open();
            }
        catch (Exception e)
            {
            e.printStackTrace();
            }
        }

    /**
     * Open the window.
     */
    public void open()
        {
        display = Display.getDefault();
        shell = new Shell();
        shell.setSize(319, 99);
        shell.setText("SWT Application");
        // ###
        lblHelp = new Label(shell, SWT.NONE);
        lblHelp.setBounds(10, 10, 291, 17);
        lblHelp.setText("Haue 'g' für weiter / 'f' für Dateidialog");
        // ###
        lblCounter = new Label(shell, SWT.NONE);
        lblCounter.setBounds(10, 44, 72, 17);
        lblCounter.setText("Counter");

        display.addFilter(SWT.KeyDown, new Listener()
            {
                public void handleEvent(Event e)
                    {
                    keyhandler(e);
                    }
            });
        shell.open();
        shell.layout();
        while (!shell.isDisposed())
            {
            if (!display.readAndDispatch())
                {
                System.out.println("dispatched " + System.currentTimeMillis());
                display.sleep();
                }
            }
        }

    private void keyhandler(Event e)
        {

        System.out.println("Keyhandler " + e.toString());

        // if (e.keyCode == 'g') sample_method();
        if (e.keyCode == 'f')
            {
            load_file();
            }

        if (e.keyCode == 'g')
            {
            display.asyncExec(new Runnable()
                {
                    public void run()
                        {
                        new BackgrounUIJob().start();
                        }
                });
            System.out.println("raus aus asyncexec");
            }
        }

    private void load_file()
        {
        FileDialog fd = new FileDialog(shell);
        fd.open();
        }

    public class BackgrounUIJob extends Thread
        {

        public void run()
            {
            display.asyncExec(new Runnable()
                {
                    public void run()
                        {
                        lblCounter.setText("Anfang");
                        }
                });
            for (int i = 0; i < 10; i++)
                {
                final int j = i;
                display.asyncExec(new Runnable()
                    {
                        public void run()
                            {
                            lblCounter.setText(j + "");
                            }
                    });
                System.out.println(i);
                // while (display.readAndDispatch()) {}
                try
                    {
                    Thread.sleep(1000);
                    }
                catch (InterruptedException e1)
                    {
                    }
                }
            display.asyncExec(new Runnable()
                {
                    public void run()
                        {
                        lblCounter.setText("Ende");
                        }
                });
            }
        }
    }
 
Zuletzt bearbeitet von einem Moderator:

Sonecc

Gesperrter Benutzer
Wie immer sollte gelten, dass lange dauernde Aktionen nicht im UIThread laufen dürfen. Reagiert die GUI aber nicht mehr, ist genau das der Fall

Genau das missachtest du in deiner sample_method, die du ja explizit im UIThread via asyncExec ausführst.
Stattdessen solltest du das in einem eigenen Thread ausführen und dort dann das asyncExec nutzen um die UI zu aktualisieren. (Soweit ich das sehe hat SirWayne das in dem geänderten Beispiel entsprechend für dich umgesetzt)
 

Bernd Hohmann

Top Contributor
Ich vermute mal du willst sowas in der Art, dein Code einfach abgerändert... Jetzt kannst mehrmals keys absetzen und du hast konkurrienden Zugriff auf das label usw. Aber mit Threads lagert man länger laufende Aktion aus...

Das ist soweit in Ordnung, aber wie löst man das Problem möglichst pragmatisch? Ich hab ja nur ein kleinstes lauffähiges Beispiel gepostet und kann mir nicht so recht vorstellen, dass in einem grösseren UI da jedesmal ein solcher Zirkus gemacht wird.

Zumal mir SWT insich etwas komisch vorkommt: der ProgressBar (zumindest unter GTK/Linux) scheint intern die Eventqueue abzuarbeiten um die Updates auf den Bildschirm zu bekommen.

Ich überlege gerade ob ich selber eine Art MessageBus in einem separaten Thread aufmache und Nachrichten der Form "new UpdateProgressBar(min, pos, max)", "new ShowConfigWindow()", "new LoadFiles()" verschicke die dann von einer zentralen Stelle aus abgearbeitet werden. Aber das ist mir noch zu unausgegoren.

Bernd
 
G

Gast2

Gast
Das ist soweit in Ordnung, aber wie löst man das Problem möglichst pragmatisch? Ich hab ja nur ein kleinstes lauffähiges Beispiel gepostet und kann mir nicht so recht vorstellen, dass in einem grösseren UI da jedesmal ein solcher Zirkus gemacht wird.
Genau dafür gibt es die Job API und ProgressMonitor usw.

Aber ist doch eigentlich logisch, dass wenn du einen langlaufende Aktion hast dafür einen NEUEN Thread brauchst um dein einzigen UIThread nicht zu belasten. Ist in Swing genau das gleiche und ich denke in JavaFX wird sich daran auch nichts ändern.
 

Sonecc

Gesperrter Benutzer
Das ist soweit in Ordnung, aber wie löst man das Problem möglichst pragmatisch?
Wenn es eine reine SWT Anwendung ist: Durch ein verünftiges Design-Konzept.

Ich wüsste ehrlich gesagt nicht, was du tun willst, dass du solche Probleme darin siehst, für deine Background-Operationen einen eigenen Thread aufzumachen und dann den Regeln entsprechend auf die UI zuzugreifen. Mal davon abgesehen, dass das in Swing nicht anders ist. SWT gibt das in die Verantwortung des Programmierers, während Swing es einem abnimmt, dafür aber nicht im MainThread ausgeführt werden sollte.
Das ganze liegt nunmal an der Art und Weise wie ein OS die GUI Elemente behandelt. Ein GUI Framework muss nunmal ein Event-Dispatching betreiben und damit die vom OS geworfenen Events auffangen. Wenn du nun in einem dieser Events eine lange dauernde Operation ausführst, kann auch die GUI nicht mehr reagieren, da du ja den Event-Dispatch blockierst. Das ist nichts besonderes und nichts außergewöhnliches, sondern schlicht eine typische Eigenschaft von UI-Frameworks.
Der Aufruf von asyncExec oder syncExec folgt dem Ansatz nur und zwar indem dein Event (z.B. das setzen eines Textes) in die Queue aufgenommen wird und entsprechend verarbeitet wird.

Es gilt (in jedem UI-Framework), dass lange dauernde Operationen nicht im UI-Thread ausgeführt werden sollten, sondern dass dafür ein eigener Thread eröffnet werden sollte. Der Zugriff auf die Elemente sollte dann wieder im UI-Thread erfolgen. Bei SWT geht das nunmal über (a)syncExec(...).

Ich programmiere nun schon seit 5 Jahren mit SWT und habe nie Probleme mit diesem Konzept gehabt und habe auch nie übermäßig viel "Zirkus" betreiben müssen. (Wobei einem RCP bzw. JFace in einigen Situationen helfen können)
 

Bernd Hohmann

Top Contributor
Danke für die Tips aber ich glaube wir missverstehen uns da: mir ist das Kernproblem schon bekannt aber ich sehe irgendwie nicht die Lösung wie man das in einer etwas anspruchsvolleren Anwendung (die zb. an allen Ecken und Kanten längerlaufende Prozesse und UI-Updates hat) sinnig löst. Letztendlich kann es nicht sein, dass ich da im Code wie der Sandmann überall asyncExec(...) einstreue.

Vielleicht hilft es die Applikation zu schildern: Das ist eine Schnellverarbeitung für Sportbildberichterstatter (in etwa wie Photo Mechanic) die komplett über die Tastatur gesteuert wird. Die Tastatur ist in Bänke aufgeteilt (Beispiel: beim Tagging haben die Cursortasten eine andere Funktion als beim Cropping).

Im Prinzip ist da nix besonderes bei weil die meissten Sachen relativ flink ablaufen. Es gibt einen "keyhandler", der je nach Modus in den entsprechenden Keyhandler verzweigt. Manche Tastendrücke erzeugen einen Dialog mit eigener ED-Loop (warten bis das Fenster geschlossen wird).

"Länger Laufender Kram" ist in der App zb. das Laden der Bilder. Da muss im Main-UI nur eine Art Progressbar aktualisiert werden. Das läuft im Moment im KeyEvent und das UI wird über meinen eigenen Progressbar via .readAndDispatch() aktualisiert. Das war mir bislang auch ganz lieb so, denn solange ich selber im KeyEvent laufe kann mir kein anderes Event ungewollt dazwischenfunken.

Bislang läuft der Code trotz hoher Funktionalität noch auf sehr schlankem Fuß.

Knackpunkt ist die nächste Version, wo es eine erweiterte Nebenläufigkeit mit Batchfunktionalität in der GUI geben soll. Dh. nachdem alle Bilder von den CF-Karten kopiert sind sollen bereits in einem separaten Fenster die IPTC-Daten (Fenster auf, Fenster zu) und danach die Macros (Fenster auf, Fenster zu) erfasst werden wärend parallel die JPG in ImageData konvertiert werden (incl. Update des Progressbars im Hintergrund auf der Main-UI).

Diese Nebenläufigkeit macht mir etwas Kopfzerbrechen, vorallem weil mit dem Beenden des Subfensters ans Hauptprogramm signalisiert werden muss "bitte alle Dropboxen mit neuen Texten versehen".

Wie oben beschrieben hab ich so eine Art Eventbus ("sol lucet omnibus") im Auge was das Problem sicher mit viel Code lösen kann - aber wie ich mich dabei elegant vom ED loslöse fehlt mir jegliche Idee.

Bernd
 
Zuletzt bearbeitet:

Neue Themen


Oben