MVC Pattern mit GUI und Threads (best practice)

Hallo,

ich möchte ein Projekt mit GUI als MVC-Modell umsetzen.
Dabei möchte ich, dass Berechnungen und sonstige Aktivitäten (Datenbankabfragen etc.) im Hintergrund abgearbeitet werden und nicht die GUI einfrieren. Ich habe hier ein Beispiel aus dem Internet (http://start-at.de/wp-content/uploads/2014/03/Workshop7.zip):

Wie löst man so etwas sinnvoll mit Threads? Kann ich auf den Swinworker verzichten?

public class Main {
	public static void main(String[] args) {
		Model model = new Model();
		
		ViewGUI viewGUI = new ViewGUI();
		ControllerGUI controllerGUI = new ControllerGUI(model, viewGUI);
		
		ViewConsole viewConsole = new ViewConsole();
		ControllerConsole controllerConsole = new ControllerConsole(model, viewConsole);
			
	}
	
}
import java.util.Observable;
import java.util.Observer;

public class ControllerConsole implements Observer {

	Model model;
	ViewConsole view;

	public ControllerConsole(Model model, ViewConsole view) {
		this.model = model;
		this.view = view;		
		model.addObserver(this);
		
	}

	@Override
	public void update(Observable arg0, Object arg1) {
		if (arg0 == model) {
			view.consoleOutput(String.valueOf(model.getZahl()));
		}
	}

}
import java.util.Observable;


public class Model extends Observable {

	private int zahl = 0;
	
	public void zaehlen(){
		zahl=(zahl+1)%3;
		setChanged();
		notifyObservers();
	}
	
	public int getZahl() {
		return zahl;
	}
	
}
import java.awt.FlowLayout;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextField;


public class ViewGUI extends JFrame {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	public static final String HOCHZAEHLEN = "HOCHZAEHLEN";
	
	private JTextField text = new JTextField(3);
	private JButton knopf = new JButton();
	
	public ViewGUI(){
		this.setTitle("Workshop 7 GUI MVC - start-at.de");
		
		this.setDefaultCloseOperation(EXIT_ON_CLOSE);
		
		this.setLayout(new FlowLayout());

		text.setEditable(false);
		this.add(text);
		
		knopf.setText("hochzaehlen!");
		knopf.setActionCommand(ViewGUI.HOCHZAEHLEN);
		
		this.add(knopf);
		
		this.pack();
		
	}
	
	public JTextField getText() {
		return text;
	}
	
	public JButton getKnopf() {
		return knopf;
	}
	
}
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Observable;
import java.util.Observer;


public class ControllerGUI implements Observer, ActionListener {

	private Model model;
	private ViewGUI view;
	
	public ControllerGUI(Model model, ViewGUI view){
		this.model = model;
		this.view = view;
		
		model.addObserver(this);
		
		view.getKnopf().addActionListener(this);
		view.getText().setText(String.valueOf(model.getZahl()));
		
		view.setVisible(true);
	}
	
	@Override
	public void update(Observable arg0, Object arg1) {
		if(arg0 == model){
			view.getText().setText(String.valueOf(model.getZahl()));
		}
		
	}

	@Override
	public void actionPerformed(ActionEvent ae) {
		switch (ae.getActionCommand()) {
		case ViewGUI.HOCHZAEHLEN:
			model.zaehlen();
			break;

		default:
			System.out.println("Unbekannte Action " + ae.getActionCommand());
			break;
		}
		
	}

}

Normalerweise übergibt man Änderungen, die an der GUI gemacht werden sollen dem EDT (Event Dispatcher Thread).
Operationen, wie z.B. Berechnungen, die mehrere Sekunden benötigen, sollten mit SwingWorker durchgeführt werden, damit die GUI noch bedienbar bleibt. Um Threads kommst du also nicht herum.

Hier noch eine Variante mit SwingUtilities

{
	(new Thread(new Runnable() // Thread-Auskopplung
	{
		public void run()
		{
			//...
			SwingUtilities.invokeLater(new Runnable() // Wiedereinkopplung von GUI-Änderungen in den EDT
			{
				public void run()
				{
					//...
				}
			});
		}
	})).start();
}```

[QUOTE=lol]Hallo,
Wie löst man so etwas sinnvoll mit Threads? Kann ich auf den Swinworker verzichten?
[/QUOTE]

Es hängt ein bißchen davon ab, WAS genau dort gemacht werden soll. Oft ist es ja so, dass der Hintergrundthread irgendwie mit dem GUI interagieren soll, z.B. Zwischenergebnisse oder den Progress anzeigen. Und dafür bietet der SwingWorker schon vorgefertigte Funktionen. Man könnte, wenn man wollte, aber auch auf den SwingWorker verzichten, und dafür das, was der SwingWorker macht, selbst (händisch) mit Threads nachbauen (so wurde das ja auch früher gemacht, als es den SwingWorker noch nicht gab ;-))

Habe es jetzt mit einer Hilfsklasse gelöst:

import java.awt.event.ActionEvent;


public class SwingThread implements Runnable {

	private ActionEvent ae;
	private Model model;
	public SwingThread(ActionEvent ae, Model model) {
		this.model = model;
		this.ae = ae;
	}
	
	public void run() {
		System.out.println("im Thread!");
		switch (ae.getActionCommand()) {
		case ViewGUI.HOCHZAEHLEN:
			model.zaehlen();
			break;

		default:
			System.out.println("Unbekannte Action " + ae.getActionCommand());
			break;
		}
		
	}

}

Im Event starte ich dann den Thread:

	public void actionPerformed(ActionEvent ae) {
		new Thread(new SwingThread(ae, model)).start();		
	}

invokelater benötige ich in meinem Beispiel doch nicht oder? Hat invokelater nicht etwas damit zu tun, falls mehrere Threads an der GUI Dinge ändern und invokelater sorgt in dem Fall für Threadsicherheit?
In welcher Klasse würde ich denn sinnvollerweise den Swinworker implementieren? ControllerGUI?

Oh doch, du brauchst trotzdem immer noch z.B. SwingUtilities.invokeLater(). Warum ? Weil du mit deinem eigenen Thread außerhalb des EDT bist. Änderungen an der GUI dürfen aber nur durch eben diesen durchgeführt werden. Und um diese Änderungen dann wieder in den EDT einzukoppenl braucht man halt z.B. SwingUtilities.invokeLater(), denn sonst griefst du ja von deinem Model aus direkt auf das GUI-Element zu und bist damit außerhalb des EDT.

Ja, das wird gerne übersehen, auch bei den Swing-Internen Klassen

void executedInDifferentThread() {
    someTableModel.changeSomething(); 
}

das SCHEINT erstmal nichts am GUI zu verändern, aber natürlich hängt an einem TableModel wieder die JTable als Listener, die durch die Änderungen dann rausgehauen werden kann.

(WO der geeignetste Platz ist, um so eine Entkopplung vorzunehmen … darüber habe ich schon gelegentlich nachgedacht, aber noch ein “Silver Bullet” gefunden, es gibt da mehrere Optionen…)

Ja, stimmt schon, so wirklich “die eine richtige Idee” wird es wohl vermutlich nicht wirklich geben, schon alleine weil es auch schon immer einen Unterschied macht ob man denn überhaupt aus dem EDT auskoppelt und dadurch nachher zu einer Wiedereinkopplung gezwungen ist oder ob man direkt im EDT arbeitet, was man ja eigentlich nicht soll.
Auch ist es immer vom verwendeten Framework und dessen Implementierung abhängig, auch was VM und OS angeht darf nicht außer acht gelassen werden.

Gut, vielleicht war meine Formulierung “Änderungen an der GUI dürfen aber nur durch eben diesen durchgeführt werden.” nicht so perfekt. “dürfen” ist vielleicht nicht der treffende Ausdruck. Natürlich ist es möglich auch von einem externen Thread Änderungen durchzuführen. Und in den meisten recht einfachen GUIs stellt das auch noch nicht so den kritischen Punkt dar. Was aber immer gerne vergessen/übersehen wird bzw was viele nicht wissen ist wie das mit der graphischen Darstellung überhaupt intern abläuft.
Ich kenn mich da auch nicht so wirklich aus und will es auch nicht breiter ziehen als es ist, fakt ist nur : man sollte darauf achten das alle Änderungen die direkt oder indirekt (übrigens schönes Beispiel von Marco mit dem Model und dem “versteckten” Listener) im EDT ablaufen und über die vorhandenen Möglichkeiten wieder in diesen eingekoppelt werden wenn man in der Event-Behandlung in einen extra Thread auskoppelt.

Aaaalso, der SwingThread den du gepostet hast ist mMn so ziemlihc worst practice. Willst du in diese Klasse, die nicht aussagt was sie macht, alle deine Action Commands in einen switch-case packen und dort abarbeiten. Das ist ineffizient und unsauber. Gerade sowas wie “hochzählen” braucht nicht in einen eigenen Thread. Ich würde erstmal gerne wissen für was du da eigentlich glaubst Threads zu benötigen, bevor ich dir rate irgendwas mit SwingWorker ua. zu unternehmen.

Also vorweg: Ich bin nur zufällig auf das Beispiel im Internet gestoßen und dies setzt das MVC-Pattern ohne Threads um.
Mir geht es nur allgemein um die „best pratice“ bei MVC mit Threads. Ich schreibe derzeit eine etwas komplexe GUI wo bei Events verschiedene komplexe Aufgaben erledigt werden (Datenbank-Abfrage, serielle Schnitstelle, …). Aus diesem Grund möchte ich es gerne mit Threads umsetzen und daher meine Frage nach der best practice.

Wie würde man es denn sinnvollker weise umsetzen?
Wenn ein Event gefeuert wird einen Thread erzeugen? Wie würde man es in folgenem Beispiel lösen:
Bei klick auf einen Knopf soll der Wert aus einem JTextField genommen und in dem Thread verarbeitet werden. Ist dies zu Ende soll an einer andere Stelle in der GUI ein JLabel mit dem Ergebnis gesetzt werden. In meinem geposteten Beispiel im ersten Post wird dies ja im erzeugen Thread zurück geschrieben ( view.getText().setText(String.valueOf(model.getZahl())):wink: ohne auf invokelater zu achten.

Gut, zwei Punkte was du beachten musst :

  1. Alles was irgendwie “länger” dauert, sei es ein einfaches Timer-Update, eine Datenbank-Anfrage, ein File-Download oder sonst was hoch-komplexes muss es definitiv aus dem EDT über einen weiteren Thread ausgelagert werden. Warum ? Relativ einfach erklärt : der EDT kümmert sich um “alles”, das beinhaltet auch Events vom OS verarbeiten (auch Mausklicks und Tastatureingaben reagieren), Updates der GUI durchführen (neu rendern um aktuelle Daten anzuziegen) sowie, wenn nötig, auch über Hard-Peers noch anderen Krams mit dem OS.
    Was passiert jetzt wenn du deine “längere” Aktion IM EDT ausführen würdest ? Richtig : du würdest seine weitere Arbeit blockieren. Dann kommt es zu diesem all-bekannten “einfrieren” oder “es wird nur das letzte Ergebnis angeziegt”. Also eigentlich alles was irgendwie eben genau nicht gewollt ist.
    Klinkt man seine Arbeit in einen externen Thread aus kann der EDT weiter seine Aufgabe erledigen und nebenbei wird parallel (darum nennt man das auch Nebenläuffigkeit) abgearbeitet.

  2. Änderungen sollten nur durch den EDT selbst ablaufen ! Erklärt hab ichs ja schon, aber vielleicht noch mal an einem konkreten Beispiel um auch aufzuzeigen warum es rein zufällig in deinem Beispiel auch komischerweise so funktioniert.
    Bleiben wir ruhig mal beim Beispiel der Datenbank-Abfrage und nehmen an dass das Ergebnis z.B. in einer Tabelle angezeigt werden soll.

FALSCH :
Du führst die Änderungen direkt in deinem eignen Thread durch und manipulierst damit die GUI.
Was kann passieren ? Nun, wenn z.B. der EDT gerade zur Hälfte mit seinem repaint() durch ist und bis dahin noch keine Daten vorhanden waren ist der obere Teil der Tabelle leer, nun gibt es aber Daten und bei der nächsten Zeile steht plötzlich was da. Nicht nur das die Ausgabe unvollständig ist befindet sich die GUI zumindest zu genau diesem Zeitpunkt auch in einem eher undefinierten Zustand.

RICHTIG :
In deinem Thread kommt die Antwort der Anfrage an und du verarbeitest das ganze erstmal, baust dann auf einem BackBuffer die Tabelle soweit zusammen und räumst auf. Während der EDT gerade gemütlich beim repaint() ist gibst du ihm dabei synchron eingekoppelt die Info : “Hey, du, da gibbet neue Daten … mache ma”. Der EDT führt nun sein aktuelles repaint() erstmal ganz sauber zu Ende. Danach ist die Tabelle erstmal weiterhin leer und die GUI in einem definierten Zustand. Am Ende seines Zyklus kommt jetzt deine Aktualisierung dran und der EDT ändert entsprechend die Tabelle und rendert sie danach. Das du zwischen durch die leere Tabelle nicht siehst liegt einfach daran das es einfach nur wahnsinnig schnell passiert.

Warum funktioniert jetzt aber dein setText() komischerweise trotzdem ? Nun, eine vereinfachte Antwort wäre in etwa : weil die Text-Komponenten von Swing intern so aufgebaut sind das nach einem setText() automatisch ein repaint() veranlasst wird. (Korrekterweise ist es dann doch komplizierter, aber das nur mal so grob.)
Einfacher formuliert : VOR deinem setText() macht der EDT erstmal irgendwie “nichts” da es keinen Anlass gibt die GUI ständig neu zu zeichnen. Jetzt veränderst du aber mit dem setText() den Inhalt und löst damit in den Tiefen der Implementierung etwas aus das halt den EDT dazu veranlasst das ganze neu darzustellen. Um jetzt bewusst eine Race-Condition zu simulieren wo man dann plötzlich z.B. eine verstümmelte Anzeige erhalten würde bedarf doch etwas speziellem Benchmarking.

Ich würd’ die EDT-Synchronisierung in die View stecken. Lang dauernde Operationen kannst du im Controller in eigene Threads packen. Diese rufen dann, wenn das Ergebnis da ist, die View-Methode mit den Model-Daten auf. Aus welchem Thread das passiert ist dann völlig unerheblich da sowieso die View sich um das EDT-Gedöns kümmert.

Ich empfehle dir das SwingWorker Tutorial: http://docs.oracle.com/javase/tutorial/uiswing/concurrency/worker.html

Ein verwirrender Tipp, wie ich finde, da die Beispiele aus dem Tutorial ja zeigen wie man in der View Logik unterbringt. IMO müsste das ganze so aussehen:


public class MyModel {
  private String text1;
  private String text2;

  // Getter/Setter, andere Properties, etc.
}

public interface MyView {
  public showMyModel(final MyModel m);
}

public class MyController() {
  private static class LongOperation { 
     MyController ctrl;
 
     public LongOperation(MyController ctrl) {
         this.ctrl = ctrl;
     }

     @Override
     public void run() {
        // irgendwas tun das lange dauert und model zurück liefert
       ctrl.onNewData(model);
     }
  }

  private MyView myView;

  public void processInput() {
    Thread t = new LongOperation(this);
    t.start();
  }

  public void onNewData(MyModel m) {
     this.myView.showMyModel(m);
  }
}

Und dein „echtes“ Model ist dann ein Fenster das in die jeweiligen UI-Elemente innerhalb eines invokeLater-Calls aufruft. Ca. so:

public class MySwingView extends JForm implements MyView {
   // Initialisierungs und UI zeug

   @Override
   public void showMyModel(final MyModel m) {
      SwingUtils.invokeLater(new Runnable() {
          // aufruf der setter der UI elemente. 
          txtBx.setText(m.getText1());
          // etc.
      });
   }
}

Hier mal ein schönes negativ-Beispiel was so alles schief gehen kann wenn man es ohne Threads versuchen will : [noparse]http://www.java-forum.org/netzwerkeprogrammierung/162786-swing-haengt-verbindung-server.html[/noparse]

@schlingel , ich weis zwar nicht auf was du hinauswillst, aber wenn du meinst es besser als die Menschen von Oracle zu wissen bitteschön. MVC is sowas, was viele so unterschiedlich interpretieren dass es richtig leidig ist darüber zu diskutieren. Bei dem Code den du geschrieben hast kenne ich mich jedenfalls nicht aus. Was ist eine LongOperation, was eine “public showMyModel(…)”, wo gehören die processInput und onNewData Methoden hin… kompilier das doch erstmal.

Das ist Pseudo-Code mit Java-Geschmack. FYI: Pseudo-Code ist zur Demonstration und nicht Verwendung gedacht.

Es ging hier darum zu zeigen wie man:

  1. Etwas in MVC strukturiert
  2. Den EDT mit einem Thread synchronisiert ohne sich Controller-Code mit View-Code (SwingUtils) zu verschandeln.

Wo habe ich das behauptet? Ich habe nur fest gestellt, dass es verwirrend ist wenn man MVC in die Gleichung nimmt und sich die Beispiele aus dem Swing-Utils-Tutorial anschaut, die Business Logic Code - welcher bei MVC in’s Model oder in den Controller gehören würde - in der View stecken.

Nein. Man kann es auf verschiedene Arten implementieren aber die Architektur ist immer gleich. Deshalb ist’s ja ein Architektur-Pattern.

MVC in a Nutshell:
Der Controller nimmt Anfragen entgegen. Diese werden verarbeitet in dem das Model mit den Daten konfrontiert wird und das Ergebnis daraus in die View gesteckt wird.
Das Model bildet die Daten ab und die Logik mit diesen zu arbeiten.
Die View bietet eine Schnittstelle um eine bestimmte Modell-Klasse darzustellen.

Wer das anders sieht liegt falsch.

Der Controller reagiert auf neuen Input mit der Methode processInput. Wenn neue Daten ausgespuckt werden, bietet der Controller die Handler-Methode onNewData an. Diese Methode wird von der lang dauernden und daher in einen eigenen Thread ausgelagerte Operation LongOperation aufgerufen. showMyModel ist die Methode die, die Model-Klasse MyModel anzeigt.

Wie gesagt: Sinn der Sache ist zu zeigen wie man View-Code, wie die SwingUtils, in der View lassen kann und trotzdem einen Thread im Controller verwenden kann.

Falls dir das noch immer zu abstrakt ist, hier zwei Artikel die ich geschrieben habe zu diesem Thema:
MVC, MVVM und MVP für Javascript – Vergleiche, Unterschiede und Empfehlungen - Eher Theorie der Pattern und wenig Code.
Android mit MVP strukturieren es gibt auch eine dazugehörige Beispiel-App: AbsorbReader Falls du eine größere App sehen möchtest die mit einem MVC ähnlichem Muster strukturiert ist, kann ich dir noch den MentalMathX-Code anbieten.

Zusammenfassend: Die SwingUtils sind eine sehr gute Möglichkeit mit dem EDT zu arbeiten. Sollten aber auf keinem Fall im Controller aufgerufen werden.

Ist letzten Endes eigentlich ziemlich egal ob man den im View oder sonst wo stehen hat, es würde lediglich seine Position in der Call-Reihenfolge ändern.
Ich stimme dir lediglich soweit zu das es sinnvoll ist so wie du es dargestellt hast da man so sichergeht dass das View selbst dafür sorge trägt das definitiv SwingUtils gecallt werden, gleich ob dies bereits vom Caller getan wurde oder nicht und so eine weitere Sicherung darstellt. Relevant wird das jedoch erst wenn man in einem Team arbeitet und die Module untereinander „public“ ausgetausch werden.

Einen Abstraktionsschritt höher: Ich finde, die Verwendung von SwingWorker stößt auch schnell an Grenzen. Wenn man während der Arbeit dann noch einen (ggf. modalen) Fortschrittsdialog anzeigen will, wird’s ziemlich schnell ziemlich kompliziert. Es gibt dann zwar den ProgressMonitor, aber das passt auch nicht immer. (Deswegen habe ich mir zuletzt ein bißchen Utility-Infrastruktur für sowas gebaut, im Sinne von SwingTaskExecutors.create(swingWorker).setModal(true).setCancelable(true).build().execute();, aber das nur nebenbei…)

Also auf Wikipedia findet man diese Graphik:
wiki:

Und in einem Artikel von einem Mann der sich damit sehr viel besser auskennt als wir alle:
fowler:

Fast jede Beschreibung von MVC stellt dieses Diagramm zwar ähnlich, aber selten komplett gleich dar und ich würde nicht sagen die liegen alle falsch.