Hauptfenster wird nicht angezeigt



  • ich habe eine recht umfangreiche Programmoberfläche, die von verschiedenen Messgeräten Werte ausliest und anzeigt. Dabei verwende ich von der Firma Meilhaus mehrere IO-RedLab-Boxen (USB) sowie die RS232-Schnittstelle für weitere Geräte. Die Werte werden über Threads abgerufen und über Set-Methoden in eine Klasse geschrieben. Ein Timer auf meiner Hauptform zeigt über Get-Methoden die Werte dann an.

    Nun zu meinem Problem. Der Code wird fehlerfrei kompiliert. Nach dem Linken wird ja normalerweise in der Windows-Taskleiste das Programm angezeigt. Kurz darauf erscheint üblicherweise das Hauptfenster. Bei mir wird das Programm in der Taskleiste zwar nach dem Linken angezeigt, aber das Hauptfenster erscheint nicht. Fehler treten auch nicht auf. Starte ich direkt die exe-Datei ist es genauso. Man muss dann sogar das Programm über den Taskmanager mit STRG+ALT+ENTF beenden. Über die Entwicklungsumgebung kann man es wie gewohnt mit STRG+F2 beenden. Fehlermeldungen kommen nicht.

    Was komisch ist, dass es teilweise nach mehreren Versuch dann doch mal funktioniert. Ich bin ratlos und wäre für Hilfe dankbar.
    Ich arbeite mit dem BorlandBuilder 5 Professional. OS: WIN7



  • So ohne Code kann man dazu nicht viel sagen...

    Allerdings machen mich dann Dinge wie 'Timer und Get-Methode' schon sehr skeptisch. Dieses Konzept des Pollings passt nicht zu Threads. Ein Thread sollte der App melden, wenn es Daten abzuholen gibt.

    Welche Art des Zugriffschutzes bzw. welche Art der Synchronisierung wird für den Datenaustasuch mit den Threads verwendet?

    Gehe ich Recht in der Annahme, dass das Hauptfenster ordnungsgemäß angezeigt wird, wenn keine Threads gestartet werden?



  • Bau in dein Programm mal Debug Ausgaben ein, um zu sehen, was wo wann passiert. Wenn du die Funktion OutputDebugString benutzt kannst du den Programmablauf im Meldungsfenster des Builders sehen. Oder du, gehst mal mit dem Debugger dein Programm Schritt für Schritt durch, um zu sehen, aus welchem Aufruf die CPU nicht wieder zurückkommt.



  • Den Code zu posten würde den Rahmen hier sprengen. Ich versuch mal den Grundaufbau beispielhaft zu beschreiben:

    ich habe eine Klasse Messwerte mit folgenden Aufbau:
    classMesswerte.h:

    class Messwerte {
       private:
          double temperatur;
          double druck;
       public:
          Messwerte();
          void SetTemperatur(double temperatur);
          void SetDruck(double druck);
          double GetTemperatur();
          double GetDruck();
    };
    

    classMesswerte.cpp:

    Messwerte::Messwerte() : temperatur(0.0f), druck(0.0f)
    void Messwerte::SetTemperatur() { this->temperatur = temperatur; }
    void Messwerte::SetDruck() { this->druck = druck; }
    double GetTemperatur() { return temperatur; }
    double GetDruck() { return druck; }
    

    In meiner MainForm (bei mir "frmMain") gibt es für jedes Messgerät einen Button, der ein weiteres kleines Fenster öffnet. Dort kann der benutzer bestimmte Einstellungen zu dem Messgerät ändern (z.B. Port, Drehzahl bei einer Pumpe usw.) Da einige Messgeräte mehrfach vorkommen, habe ich für jedes Messgerät eine Klasse geschrieben bzw einen Thread. Dort sind Methoden zum Verbinden sowie die Erfassung der Messdaten in der Execute()-methode (hier das Beispiel anhand eines Messgeräts der Firma Emko:

    in der Headerdatei erzeuge ich das Objekt "messwerte" der Klasse "Messwerte".
    hier die cpp:

    __fastcall Emko::Emko(bool CreateSuspended) 
       : TThread(CreateSuspended),
         status(false)
    {
    	Priority = tpHighest;
    }
    //---------------------------------------------------------------------------
    __fastcall Emko::~Emko()
    {
       if(status) rs232.CloseComm();
    }
    //---------------------------------------------------------------------------
    void __fastcall Emko::Execute()
    {
      while(!Terminated) {
        ErfasseWerte();
        Sleep(1000);
      }
    }
    //---------------------------------------------------------------------------
    void Emko::PortOeffnen(AnsiString port)
    {
      status = rs232.OpenComm(port.c_str()); //rs232 ist ein Objekt einer Klasse für RS2323
    
      if(status == true) {
        rs232.SetDCB();
        rs232.SetReadTimeouts(100, 10, 10);
      }
      else
        ShowMessage("Nicht verbunden!");
    }
    //---------------------------------------------------------------------------
    void Emko::ErfasseWerte()
    {
      if (status) {
        unsigned char send[] = {0x01, 0x04, 0, 0, 0, 0x18, 0xF0, 0};
        unsigned char rec[60];
        rs232.SendData(send, 8);
        Sleep(250);
        rs232.ReceiveData(rec, 60);
        try {
          messwerte.temperatur  = (rec[3] * 256 + rec[4]) / 10.0;
        }
        catch(...) {
          messwerte.temperatur = 0.0f;
        }
      }
    }
    

    das ist jetzt besipielhaft für ein Messgerät, das über RS232 ausgelesen wird. Bei den Geräten, die über USB mit der angesprochenen RedLab-Box ausgelesen werden, habe ich es auch so gemacht.

    Die Threads starte ich dann mit resume() bei erfolgreicher Verbindung (wenn status == true) in den Einstellungsfenstern über einen Verbinden-Button bzw automatisch im Konstruktor der Form in der Konstruktorliste über z.B. Emko(new emko(false)).

    Da das Programm teilweise funktioniert und startet und zB auch funktioniert wenn ich ich einzelne USB-Stecker aus dem USB-Hub rausziehe, vermute ich einen Konflikt mit den USB-Controllern. In einen der Threads habe ich in der Methode ErfasseWerte() beispielsweise 2 Funktionen zum Auslesen von 2 unterschiedlichen USB-Messgeräten.

    Mit den Debug werd ich morgen gleich mal ausprobieren.



  • Aktualisierst du die GUI in einem deiner Threads oder findet die Aktualisierung durch Synchronisation im Hauptthread der Anwendung statt. Die VCL ist nicht multithreadingfähig, wenn du die GUI aktualisierst muss das im Hauptthread der Anwendung geschehen.



  • die Anzeige der Werte auf der Hauptoberfläche übernimmt ein Timer, der die messwerte alle 1 Sek aktualisiert:

    void __fastcall TForm1::timMesswerteAnzeigenTimer(TObject *Sender)
    {
       labAnzeigeTemperatur->Caption = messwerte.GetTemperatur();
       labAnzeigeDruck->Caption = messwerte.GetDruck();
       //usw...
    }
    

    Die Anwendung bleibt stehen in der WinMain bei Application->Run().



  • Das heißt, du synchronisierst da nichts, sondern liest blind Werte, ohne zu wissen, in welchem Zustand sich der Thread befindet? Das kann nicht funktionieren...

    Ich würde aus dem Thread heraus eine Botschaft senden, wenn neue Messwerte bereit liegen und dann aus dem Hauptthraed heraus die Werte auslesen. Dafür brauchst du im Thread zB eine TCriticalSection, um auszuschließen, dass du verscuhst Werte zu lesen, während die gerade vom Port gelesen werden. Oder du verwendest Synchronize() aus dem Thread heraus. Ist einfacher, hat aber auch Nachteile.

    Was soll denn das Sleep() in dem Thread? Ich kenn mich mit RS232 nicht so aus, aber auch dort müsste es die Möglichkeit geben, mittels WaitForSingleObjekt() und einer entsprechenden WinAPI-Funktion darauf zu warten, dass Daten zur Abholung bereit liegen.

    Polling, so wie du es in dem Hauptthread und den Threads machst, ist ganz schlechter Stil. Und im Zusammenhang mit Threads bringt es nichts als Probleme.



  • Das heißt, du synchronisierst da nichts, sondern liest blind Werte, ohne zu wissen, in welchem Zustand sich der Thread befindet? Das kann nicht funktionieren...

    Warum interessiert der Zustand des Threads? Der wird doch mit resume() gestartet und läuft dann bis er suspended wird. Der Thread wird auch mit resume nur gestartet, wenn eine Verbindung da ist (d.h wenn status gleich true is). Ansonsten ist der Wert halt 0.

    Was soll denn das Sleep() in dem Thread?

    Das Sleep ist da, damit er sich über den Thread nur alle 1 Sekunde die Werte holt und in die Klasse Messwerte schreibt.

    dort müsste es die Möglichkeit geben, mittels WaitForSingleObjekt() und einer entsprechenden WinAPI-Funktion darauf zu warten, dass Daten zur Abholung bereit liegen

    . Die Daten können ja jederzeit über RS232 abgeholt werden. Da muss doch nicht gewartet werden.



  • Der Zustand des Threads interessiert, weil du nicht wissen kannst, ob der Thread gerade im Sleep steckt, oder gerade den Wert in die Klassenvariable schreibt, oder, oder, oder... Glaub mir, ohne eine Synchronisierung wird das niemals sauber funktionieren. Das ist wie russisches Roulette. Das geht auch nur eine gewisse Weile gut, wenn überhaupt.

    Statt des Sleeps solltest Du dann lieber einen Timer im Thread verwenden.

    Und offensichtlich muß da doch auf den Port gewartet werden. Sonst wäre doch da wohl kaum ein weiteres Sleep vor dem Auslesen der Schnittstelle.

    rudpower schrieb:

    void __fastcall TForm1::timMesswerteAnzeigenTimer(TObject *Sender)
    {
       labAnzeigeTemperatur->Caption = messwerte.GetTemperatur();
       labAnzeigeDruck->Caption = messwerte.GetDruck();
       //usw...
    }
    

    Alleine das kann schon für die Probleme verantwortlich sein. Du greifst ohne Synchronisierung auf die Threads zu.



  • ok, das seh ich ein. Bisher hab ich immer so mit Threads gearbeitet und es ging auch immer gut.
    Den Thread setze ich ja eigentlich ein um alle 1 Sekunde die Daten vom Messgerät zu holen. Deshalb die Zeile (Sleep(1000);).

    Nun möchte ich es richtig machen. Brauche ich überhaupt Threads oder kann ich einfach einen Timer nehmen, der die Daten holt?

    Sollte ich für die Messwerte vielleicht einfach ein struct nehmen und in eine eigene unit packen? Oder vielleicht als private in der frmMain deklarieren?

    Die Daten können erst vom Messgerät empfangen werden, wenn der Benutzer die verbindungsdaten einstellt und einen Button Verbinden drückt. Dies geschieht je Messgerät in einer eigenen Form. Schreibe ich am besten einen Thread für alle Messgeräte oder für jedes Messgerät einen eigenen Thread?



  • Generell ist Synchronisierung natürlich richtig. In diesem speziellen Fall ist aber nicht so schlimm, was der OP da gemacht hat. Er schreibt ja nur Werte in eine Variable, was in einem CPU Zyklus erledigt ist. D.h. der GUI Thread liest entweder noch den alten oder den neuen Wert. Und selbst wenn (es könnte Probleme bei den Double Werten geben), dann würde ja nur ein falscher Wert gelesen mit dem nichts anderes gemacht wird als ihn anzuzeigen - für 1 Sek wäre dann halt in seienm Fenster kein vernünftiger Wert angezeigt.

    Ich vermute eher, dass sich der VCL Thread irgendwodran blockiert.



  • ich bekomm ja auch keinen Fehler sondern das Programm startet einfach nicht. Ich denke mittlerweile, dass es an den vielen USB liegt, die über einen USB Verteiler am Rechner angeschlossen sind. Zieh ich einzelne USB raus funktioniert es tadellos. Es ist aber auch nicht bei bestimmten USB-geräten.
    Ich vermute, dass es Konflikte gibt mit dem USB-Hostcontrollern. ich glaub der rechner besitzt 4 Stück davon.
    Bei Festplatten ist es ja auch so. Wenn man von einer zur anderen Festplatte kopieren will, die an einem Hostcontroller hängen, scheitert dies.



  • Dann wirf doch mal den Debugger an und schau nach, wo das Prgramm hängt...

    Und wer glaubt, dass man nicht synchrinisieren muss, wenn man doch nur ein paar Daten aus dem Thread liest - ja der hat leider schon verloren. Ich kann nur noch einmal dringend davon abraten, ohne Zugriffsschutz auf Threads zuzugreifen.

    Ich kann auch nur einen Blick in die WinAPI empfehlen. Da gibt es eine ganze Reihe nützlicher Events zur Threadsteuerung. Und die CriticalSection aus der WinAPI hat gegenüber der TCriticalSection den Vorteil, dass es ein TryEnter() gibt.

    Ich würde ein Event definieren, dass das Ende des Threads aus dem MainThread heraus sinalisiert. Auf dieses Event wartest du in der Execute des Threads mittels WaitForSingleObject(). Den Timeout auf 1000 ms gesetzt - und schon brauchst du nicht mal mehr einen Timer im Thread. Immer wenn du in den Timeout läufst, liest du die Daten vom Gerät...
    Und wenn es eine Möglichkeit gibt, mit einen Event zu signalisieren, dass die Daten vom Gerät geschrieben wurden (und das sollte es eigentlich geben), dann tausche WaitForSingleObject() gegen WaitForMultipleObject() und du bist auch das zweite Sleep los.

    Wenn ich mir die Threadklasse so ansehe, wage ich schon fast zu bezweifeln, dass der Destruktor des Threads überhaupt noch aufgerufen wird... Und die Tatsache, dass das Programm nach mehreren Versuchen dann doch mal startet, deutet - meiner Meinung nach - sehr stark auf ein Problem mit den Threads hin.

    Multithreading ist allerdings zu komplex um das hier in ein paar Posts zu erläutern, da muss man sich einarbeiten. Wenn du das nicht möchtest, verzichte lieber auf Threads.

    @Morle: Ich weiß nicht, in welchem Bereich der Messtechnik rudpower arbeitet, aber bei uns kostet ein einzelner falscher Messwert Geld. Denn das bedeutet, dass der Sensor nicht einwandfrei arbeitet und verschrottet oder zumindest nachbearbeitet werden muss.



  • kann ich in der Execute des jeweiligen Threads nicht einfach die Daten auf der Hauptoberfläche anzeigen lassen?

    void __fastcall Emko::Execute()
    {
      while(!Terminated) {
        ErfasseWerte();
        frmMain->messwerte.temperatur = temperatur;
        Synchronize(UpdateCaption);
      }
    }
    
    void __fastcall Emko::UpdateCaption()
    {
       frmMain->txtAnzeigeTemperatur->Text = FloatToStr(temperatur);
    }
    
    void Emko::ErfasseWerte()
    {
      if (status) {
        unsigned char send[] = {0x01, 0x04, 0, 0, 0, 0x18, 0xF0, 0};
        unsigned char rec[60];
        rs232.SendData(send, 8);
        Sleep(250);
        rs232.ReceiveData(rec, 60);
        try {
          temperatur  = (rec[3] * 256 + rec[4]) / 10.0;
        }
        catch(...) {
          temperatur = 0.0f;
        }
      }
    }
    


  • Fast... Nur die Zeile

    frmMain->messwerte.temperatur = temperatur;
    

    muss noch mit in die synchronisierte UpdateCaption().
    Du darfst ohne Zugriffsschutz auf nichts außerhalb des Threads zugreifen.


Anmelden zum Antworten