Threads und Observer

Status
Nicht offen für weitere Antworten.

vsk

Aktives Mitglied
Hallo, ich habe so meine Probleme mehrere Threads (MVC ähnlich) und das Observer-Pattern zu verheiraten.

Am Ende sollen es 3 Threads sein
Controller Thread
View Thread
Model Thread

Das aktuelle Problem ist, dass ich es nicht schaffe den View Threads mittels Observer update zu aktuallisieren. Im ControllerThread wird eine Tastatureingabe Abgefragt und in Abhängigkeit von der Eingabe soll die View entsprechend aktuallisiert werden.

Meine Main
Java:
public class Main {
    public static void main(String[] args) {
        ControllerKlasse myController = new ControllerKlasse();
        Thread s = new Thread(myController);
        s.start();
    }
}

ControllerKlasse
Java:
import java.util.Observer;
import java.util.Observable;

public class ControllerKlasse extends Observable implements Runnable{
    ViewKlasse myView = new ViewKlasse();
   // @Override
    public void run(){
        myView.start(); //View Thread starten
        this.MenueController();
    }
    /**
     * Methode zur Auswertung der Benutzereingabe
     */
    public void MenueController(){
        System.out.println("MenueController");
        int menueNr = StdInput.readInt();
        System.out.println("gelesen in MenueController: " + menueNr);
        if (menueNr < 1 || menueNr > 6) {		// if-Anweisung zur Pr�fung richtiger Programmpunkt
			System.out.println("Auswahl: " + menueNr + " unbekannt!");
			System.out.println("\nTreffen Sie eine neue Wahl");

		} else {
			switch (menueNr) {
			case 1: //die Anderen Cases fehlen im Moment
                            System.out.println("Case 1");
                            setChanged();
                            //myView.notify();
                            notifyObservers(menueNr);
                            System.out.println("Case 1 durchlaufen");
				break;
			default:
			}//switch
                }//else
    }
}

ViewKlasse
Java:
import java.util.Observer;
import java.util.Observable;

public class ViewKlasse extends Thread implements Observer{

    @Override //wir Ueberschreiben die run() Methode von java.lang.Thread
    public void run(){
        this.Menue();
        try{
            while(true){
             //läuft im Moment "ewig"           
        }
        }catch (Exception e){};
        
    }

    /**
     * Methode Menue() zeigt das textbasierte Auswahlmenue auf der Konsole an
     */
    public void Menue(){
        //ein Auswahlmenue wird angezeigt
    }

    public void update(Observable obs, Object o){ //nur Testausgaben
        System.out.println("hallo ich bin die update");
        int eingabe = (Integer)o;
        System.out.println("Die Eingabe war: " +eingabe);
    }
}
 
Du musst dem Observable (Controller) noch den Observer (View) adden. Wahrscheinlich machst du das am besten im Konstruktor des Controllers mit [c]myView.addObserver(this);[/c]

3 Threads für MVC tönt zwar sehr spannend, gehört habe ich davon aber noch nie etwas. Zumindest bei Swing läuft ja ziemlich alles über den Event-Dispatching-Thread. Oder verwendest du AWT oder SWT oder sonst etwas?
 
Hallo!
Danke fuer die schnelle Antwort.

Die 3 Threads sind eine Vorgabe. Später soll es mittels Pipes zu einem Datenaustausch zwischen den Threads kommen. Das funktioniert auch soweit.

Ich habe versucht den Controller an die View zu adden. Das funktioniert leider nicht.
Ich benutze als IDE Netbeans und bekomme die Funtion "addObserver" erst garnicht angeboten.

Ich habe mit schon gedacht, dass es daran liegt. In den Beispielen ohne Threads wird die View auch als new Observer Object erzeugt. Dann läuft es aber bei mir nicht als Thread 🙁
 
Zuletzt bearbeitet:
Sorry, ich hatte die beiden Objekte vertauscht, es ist natürlich gerade umgekehrt: [c]this.addObserver(myView);[/c]. Richtig beschrieben, aber falscher Befehl 😉

Es ist allerdings so, dass jener Thread, der im Observable notifyObservers ausführt auch alle update() der Observer ausführt. Wie du das verhindern kannst, wüsste ich jetzt auch nicht direkt, weil du kannst ja nicht wirklich einem Thread einen Befehl geben, wie du es einem Objekt kannst. Du kannst einem Thread nicht sagen, er soll jetzt ein Objekt updaten. Der Thread könnte höchstens durch ein wait() warten bis irgendein anderer Thread notify() aufruft und den Thread damit aufweckt, welcher dann ein update vollzieht.
 
Danke!
Hätte ich ja vielleicht auch merken können. Habe aber so das Gefühl ich sitze schon wieder zu lange davor und habe nen "Tunnelblick"

Leider erhalte ich immer noch keine Ausgabe von der Update Methode aus dem View-Thread. Es scheint als würde die Methode garnicht aufgerufen. Ich versteh das nicht, eigentlich unterscheidet sich mein Programm von den Beispielen doch nur im Falle der Threads und dass ich die Objecte nicht alle in der Main erzeuge.

Ich hatte eigentlich vor, das mit wait() und notify() zu regeln. Das führt allerdings dann zu
Exception in thread "Thread-1" java.lang.IllegalMonitorStateException

an der Stelle im Controller Thread an dem ich "myView.notify()" aufrufe.

Edit:
Man sollte sich einfach die Exceptions immer ausgeben lassen...

Ich erhalte den Fehler
java.lang.IllegalMonitorStateException
bereits in der Methode run() von myView
Java:
public class ViewKlasse extends Thread implements Observer{

    @Override //wir Ueberschreiben die run() Methode von java.lang.Thread
    public void run(){
        this.Menue();
        try{
            while(true){
                this.wait(); //<<< hier kommt wohl der Fehler her
                System.out.println("Habe gewartet");
        }
        }catch (Exception e){System.out.println("Error "+e);}   
    }
 
Zuletzt bearbeitet:
Wenn du den Observer dem Observable angehängt hast, müsste das funktionieren. Du kannst ja mal countObservers() auf dem Observable aufrufen und schauen, wieviele Observer du hast. Müsste wohl 1 sein, deine myView.

Um ein wait() auf einem Objekt aufrufen zu können muss es in einem synchronized Block liegen. Weil du bei dir das this synchonisierst, kannst du am einfachsten die ganze Methode run() synchronisieren, du schreibst dann einfach: [c]public synchronized void run() { ... [/c].

//EDIT: ein while(true) darum zu legen ist natürlich nicht gerade genial, weil der Thread dann ewig wartet. Du musst true halt durch einen boolean ersetzen, welcher dann zuerst auf false gesetzt wird bevor du this.notify() aufrufst.
 
So, die update Methode in myView (ViewKlasse) reagiert nun.

Danke dem Vorschlag mit countObservers() habe ich bemerkt das der Observer nicht hinzugefügt wurde. Aus irgend einem Grund klappt dies im Konstruktor von ControllerKlasse nicht.
Ich habes es jetzt in die run() Methode von ControllerKlasse aufgenommen.
Java:
public class ControllerKlasse extends Observable implements Runnable{
    ViewKlasse myView = new ViewKlasse();
    public void ControllerKlasse(){
//empty
    }
    // @Override
    public void run(){
        this.addObserver(myView);
        myView.start();
        this.MenueController();
    }
...

Jetzt gilt es heraus zu finden warum wait() auch ohne Endlosscheife einen Fehler wirft.
Werde mich später wieder melden...
 
wie oben geschrieben: synchronized

wait() funktioniert in der synchronizied run() methode. Aber notify() wirft auch innerhalb eines synchronized(this) bzw in einer synchronized Methode einen Fehler.

Schaue ich mir das Ganze nochmal an kommen mir so meine Zweifel ob es überhautp sinvoll ist.

Denn eigentlich verlege ich ja nur die Abfrage der Benutzereingabe in den Controller Thread, nur um dann in der update() Methode des ViewThreads wieder die Eingabe zu prüfen und dann entsprechend eine Methode der View auf zu rufen... Da kann ich doch gleich aus dem Controller heraus myViewMethode() aufrufen, oder? Wozu hab soll ich dann Threads verwenden?!
 
Welche eine Exception wirft das notify denn?

Ich bin auch eher skeptisch, ob all deine Threads wirklich Vorteile bringen. Zumindest für den Unterhalt der Software scheint es einige Komplikationen zu geben, die du sonst nicht hättest. Soweit ich weiss (und wahnsinn viel weiss ich zugegebenermassen nicht) läuft das MVC Pattern in der Regel nur mit einem Thread. Jedenfalls bei den Tutorials, die man so findet.
 
Immer noch
java.lang.IllegalMonitorStateException


Das Ganze soll eine Übung sein, um unter anderem mit Pipes zwischen den Threads Daten aus zu tauschen. Ein ModelThread liest aus einer Datei und schickt diese Daten dann an den ViewThread, kontrolliert und koordiniert vom ControllerThread. (Zumindest wenn ich es richtig verstanden habe)
 
Kann es sein dass du notify auf einem falschen Objekt aufrufst? Ein Objekt, das nicht synchronized ist. Auch wenn deine Methode synchronized ist, gilt das nicht für die Objekte die du in dieser Methode verwendest. Bei dem wait, da hast du ja wait() auf dem this-Objekt aufgerufen, deshalb konntest du einfach die Methode synchronized machen und das galt dann automatisch für das this Objekt. Wenn du nun notify von einer anderen Klasse her aufrufst, musst du die Referenz auf die ViewKlasse synchronized machen: synchronized(myView) { ... myView.notify() ... }
 
Ohne das mir genau angeschaut zu haben, stach mir eins ins Auge. Ist der Sinn nicht, View kennt Controller -> Controller kennt Model --> Views melden sich über Controller als Observer am Model an. Wieso instanzierst du eine View im Controller? Die Change Propagation läuft über Observer und nicht über der Controller.
 
@Dissi

ja... irgendwie ist das alles ein wenig aus dem Ruder gelaufen...
ich hab jetzt viele schönen methoden in meinem Controller welche die updateMethode der View nutzen und der ist es ganz egal in welchem Zustand der ViewThread ist...
Zudem reagiert der ViewThread nach start() auf keine weiteren Befehle wie wait() oder interrupted()...

Wenn ich dich richtig verstehe, müssten eigentlich alle meine Methoden aus dem Controller welche etwas mit der View zu tun haben, in einen ModelThread?

Die Aufgabenstellung war eigentlich...
1.Ein Thread kümmert sich um die Kommunikation mit dem
User („View“-Thread; User Interface).
2. Ein zweiter Thread kümmert sich um die Lese- und
Schreibvorgänge auf der Datenbasis („Model“-Thread).
3. Daten werden in einer CSV Datei gespeichert.
4. Der Datenaustausch und Kommunikation der beiden
Threads erfolgt mit Hilfe einer Pipe.
5. Der Hauptthread ist der „Controler“-Thread.

Ich wollte die Pipe eigentlich nur nehmen um die Daten zum Schreiben an das Model zu schicken...
 
Zuletzt bearbeitet:
Wenn du mit Swing arbeitest, wird er Event-Dispatching-Thread (EDT) gestartet, der das Zeichnen übernimmt. Der EDT (und nur der) sind eigentlich für die View zuständig. Es ist durchaus sinnvoll, wenn ein anderer Thread am Modell etwas ändert und dann die Observer verständigt, möglicherweise auch den Controller, der dann die view neu zeichnen lässt.
Ich gehe mit Diss nicht ganz einig, dass das View den Kontroller kennt. Meiner Meinung nach kennt die View das Modell und kann sich gemäss den Daten dort updaten. Der Kontroller kennt die View und das Model und er registriert Listener bei der View, die dann bei User-Interaktionen das Model verändern. Wenn jetzt noch ein anderer Thread am Model rumwerkelt, dann sehe ich im Grunde genommen zwei Varianten, wie das Model dem View befehlen kann, sich neu zu zeichnen.
1. das Model hat eine Methode notifyObserversInEDT() und führt dort folgendes aus:
Java:
SwingUtilities.invakeLater(new Runnable() {
  public void run() { // wird vom EDT ausgeführt
    ViewKlasse.this.notifyObservers();
  }
}
Dadurch kriegt der EDT (View Thread) den Befehl, die Observer (View) zu notifizieren.

2. Du lässt das Model, wie es ist und überschreibst dann in Observer (View) update und delegierst dort die Arbeit an den EDT. Weiss halt nicht, wie genau du das da gemacht hast, vielleicht reicht ein repaint(), welches ja auch vom EDT ausgeführt würde oder du musst es mit SwingUtilities.invokeLater machen.
 
Nun mit einer GUI hätte ich das ganze vielleicht leichter verstanden.
Aber die View ist im Moment einfach nur die Konsole... Das heisst der ViewThread ist im Grunde nur für die Ausgabe von Text auf der Konsole zuständig.
Dafür habe ich Methoden im Controller entworfen (die wohl in den ModelThread gehören) welche die kompletten String-Ausgaben (mehrzeilig) zustammenstellen und dann an die View senden, im Moment noch über Observer update() Methode.
Baue jetzt das ganze um, auf Pipe. Wobei mir das irgendwie altmodisch vorkommt. Schließlich muss ich jetzt immer ein Steuerzeichen senden, damit die View weiss wofür der Rest der Daten ist der durch die Pipe kommt...
 
Status
Nicht offen für weitere Antworten.

Neue Themen


Zurück
Oben