Kommunikation von TThread mit dem Anwendungsformular



  • Hallo alle miteinander! 🙂

    Da ich hier neu bin, erst einmal ein paar Dinge über mich:
    - ich kenne dieses Forum bereits seit ich angefangen habe zu programmieren
    und habe hier viele meiner Fragen beantworten können 😉
    - ich programmiere seit ca. 3 Jahren (mit dem Borland C++ Builder 6)
    - alles was ich über C++ weiß, habe ich mir in meiner Freizeit angelesen
    (daher bitte ich euch um genaue Erklärungen von Fachbegriffen, etc.)

    ---
    So, nun zum Thema:

    Ich arbeite gerade an einem Programm, das mit einem TThread-Array arbeitet.
    (Ich habe also nur eine Unit (für den Thread) und verwende diesen Thread als Array (10 Threads gesamt))

    /* MainUnit.cpp - Main Form Unit */
    
    #include "ThreadTest.h"
    ...
    
    // Thread Handle
    TTrdTest *TrdTestHandle[10]; // 10 Threads [0 bis 9]
    ...
    
    // Thread ID
    for (int TrdID = 0; TrdID < 10; ++TrdID)
    {
      // Thread starten ( Handle = Main->Handle, ThreadID, Anzahl Threads (gesamt) )
      TrdTestHandle[TrdID] = new TTrdTest(Handle, TrdID+1, 10);
    }
    ...
    
    // Thread ID
    for (int TrdID = 0; TrdID < 10; ++TrdID)
    {
      // Thread beenden
      TrdTestHandle[TrdID]->Terminate();
    }
    
    /* ThreadTest.cpp - Thread Test Unit */
    
    //---------------------------------------------------------------------------
    __fastcall TTrdTest::TTrdTest(HANDLE MainHandle, int ThreadID, int ThreadCount)
    : TThread(False)
    {
      FreeOnTerminate = true; // Thread freigeben, wenn Terminated == true
    
      // globale Variable => Main->Handle
      ThreadHandle = MainHandle;
    
      // Anzahl Threads
      TrdCnt = ThreadCount;
    
      // Thread ID
      TrdID = ThreadID;
    }
    //---------------------------------------------------------------------------
    ...
    
    //---------------------------------------------------------------------------
    void __fastcall TTrdTest::SetThreadON(void)
    {
      // Message Parameter setzen
      MsgWParam = TrdID, MsgLParam = 1; // Thread AN (1)
    
      // Message senden || Thread läuft
      SendMessage(ThreadHandle, MYWM_TrdSearch, MsgWParam, MsgLParam);
    }
    //---------------------------------------------------------------------------
    void __fastcall TTrdTest::SetThreadOFF(void)
    {
      // Message Parameter setzen
      MsgWParam = TrdID, MsgLParam = 0; // Thread AUS (0)
    
      // Message senden || Thread läuft
      SendMessage(ThreadHandle, MYWM_TrdSearch, MsgWParam, MsgLParam);
    }
    //---------------------------------------------------------------------------
    

    Frage 1: Ich habe den Header des Threads ins Main-Formular eingebunden und das Handle für den Thread als globale Variable ebenfalls im Main-Formular deklariert.
    => Gibt es eine andere (evtl. bessere) Variante wie ich das machen kann?

    Frage 2: Wenn ich den Thread mit 'Terminate()' beende und ihn später erneut mit 'new' aufrufe, gibt es doch sicher i-wann einen Speicherüberlauf!?
    => Muss ich außer 'Terminate()' noch etwas machen um den Thread "sauber" zu beenden?
    ('delete' kann ich ja nicht nehmen, da ich sonst die globale Handle-Variable löschen würde 🙄 )

    Frage 3: Ich habe aus den Threads mit 'SendMessage' eine Nachricht an einen MessageHandler im Main-Formular geschickt, wenn ein Thread gestartet oder beendet wird.
    => Gibt es eine bessere Variante dem Main-Formular den Status der Threads mitzuteilen?

    Frage 4: Ich berechne in den Threads eine Variable und möchte diese dann ans Main-Formular schicken.
    => Wie kann ich die Variable aus den Threads ans Main-Formular schicken und diese dann anzeigen lassen?

    Frage 5: Wie bereits erwähnt, habe ich mir C++ in meiner Freizeit durch Beispiele und viel lesen selbst beigebracht (ohne Lehrgang etc.)
    => Gibt es an meinem Sourcecode sonst noch etwas was ich anders machen sollte?

    ---
    Ich hoffe das sind nicht zu viele Fragen. 🙂

    Ich freue mich schon auf euere Antworten! Bitte sagt mit ehrlich, wenn ich was anders machen muss!

    -CppWannaBee



  • Nachdem ich nun den halben Tag an meinem Modelhubschraubschraub gebastelt habe
    und nebenbei auf Antworten gehofft habe, habe ich mir die Zeit genommen und ein
    Beispiel-Projekt geschrieben.

    Mit diesem Projekt möchte ich euch nochmal meine obigen Fragen näher bringen!

    ---
    - 1 Formular
    - 2 Buttons
    - 1 Memo
    - 50 Threads (Array)
    ---
    Download Project (BCB 6): http://www.xup.in/dl,15136463/test.rar/
    ---

    /* MainUnit.cpp - Main Formular */
    
    //---------------------------------------------------------------------------
    #include <vcl.h>
    #pragma hdrstop
    //---------------------------------------------------------------------------
    #include "MainUnit.h"
    #include "ThreadTest.h"
    //---------------------------------------------------------------------------
    #pragma package(smart_init)
    #pragma resource "*.dfm"
    //---------------------------------------------------------------------------
    TMain *Main;
    
    const int TrdsMax = 50;
    
    // Thread Handles
    TTrdTest *TrdTestHandle[TrdsMax]; // x Threads [0 bis x-1]
    
    // Anzahl aktiver Threads
    int TrdsActive, TrdCnt;
    
    // Zeit Start - Ende
    int Zeit;
    //---------------------------------------------------------------------------
    __fastcall TMain::TMain(TComponent* Owner)
            : TForm(Owner)
    {
    }
    //---------------------------------------------------------------------------
    
    // ##########################################################################
    // Main Formular OnClose Event
    // ##########################################################################
    
    //---------------------------------------------------------------------------
    void __fastcall TMain::FormClose(TObject *Sender, TCloseAction &Action)
    {
      for (int i = 0; i < TrdsMax; ++i)
      {
        // Threads beenden
        if (TrdTestHandle)
        {
          TrdTestHandle[i]->Terminate();
          TrdTestHandle[i] = NULL;
        }
      }
    }
    //---------------------------------------------------------------------------
    
    // ##########################################################################
    // Buttons
    // ##########################################################################
    
    //---------------------------------------------------------------------------
    void __fastcall TMain::BtnStartClick(TObject *Sender)
    {
      Zeit = ::GetTickCount();
    
      BtnStart->Enabled = false;
    
      // Threads Aktiv = 0, Thread Anzahl = 50
      TrdsActive = 0, TrdCnt = 50;
    
      for (int i = 0; i < TrdCnt; ++i)
      {
        // Threads starten (Handle = Main->Handle, TrdID = ID des Threads)
        TrdTestHandle[i] = new TTrdTest(Handle, i+1);
      }
    }
    //---------------------------------------------------------------------------
    void __fastcall TMain::BtnStopClick(TObject *Sender)
    {
      Zeit = ::GetTickCount();
    
      BtnStop->Enabled = false;
    
      for (int i = 0; i < TrdCnt; ++i)
      {
        // Threads beenden
        if (TrdTestHandle[i])
        {
          TrdTestHandle[i]->Terminate();
          TrdTestHandle[i] = NULL;
        }
      }
    }
    //---------------------------------------------------------------------------
    
    // ##########################################################################
    // Message Handler für die Threads
    // ##########################################################################
    
    //---------------------------------------------------------------------------
    void __fastcall TMain::TrdTestMsgHandler(Messages::TMessage& Msg)
    {
      if (Msg.LParam == 1) // Thread AN
      {
        TrdsActive++;
    
        Status->Lines->Add("Threads: " + String(TrdsActive) + " - TrdID: " + String(Msg.WParam));
    
        if (TrdsActive == TrdCnt) // alle an
        {
          BtnStop->Enabled = true;
    
          Status->Lines->Add("Threads gestartet. (" + String(::GetTickCount() - Zeit) + " ms)");
        }
      }
      else if (Msg.LParam == 0) // Thread AUS
      {
        TrdsActive--;
    
        Status->Lines->Add("Threads: " + String(TrdsActive) + " - TrdID: " + String(Msg.WParam));
    
        if (TrdsActive == 0) // alle aus
        {
          BtnStart->Enabled = true;
    
          Status->Lines->Add("Threads gestoppt. (" + String(::GetTickCount() - Zeit) + " ms)");
        }
      }
      else if (Msg.LParam < 0) // Zähler (pro Sekunde)
      {
        Status->Lines->Add("Thread: " + String(Msg.WParam) + " - Zähler: " + String(Msg.LParam * (-1) ) );
      }
    }
    //---------------------------------------------------------------------------
    
    /* MainUnit.h - Main Formular Header */
    
    //---------------------------------------------------------------------------
    #ifndef MainUnitH
    #define MainUnitH
    //---------------------------------------------------------------------------
    #include <Classes.hpp>
    #include <Controls.hpp>
    #include <StdCtrls.hpp>
    #include <Forms.hpp>
    //---------------------------------------------------------------------------
    // Windows Message für Kommunikation mit dem Thread
    #define MYWM_TrdTest    (WM_APP + 1001)
    //---------------------------------------------------------------------------
    class TMain : public TForm
    {
    __published:	// Von der IDE verwaltete Komponenten
            TButton *BtnStart;
            TButton *BtnStop;
            TMemo *Status;
            //-------------------------------------------------------------------
            //--- Main Formular OnClose Event
            //-------------------------------------------------------------------
            void __fastcall FormClose(TObject *Sender, TCloseAction &Action);
            //-------------------------------------------------------------------
            //--- Buttons
            //-------------------------------------------------------------------
            void __fastcall BtnStartClick(TObject *Sender);
            void __fastcall BtnStopClick(TObject *Sender);
    private:	// Anwender-Deklarationen
    public:		// Anwender-Deklarationen
            __fastcall TMain(TComponent* Owner);
    
            // Funktion zum Auswerten der Messages von den Threads
            void __fastcall TrdTestMsgHandler(Messages::TMessage& Msg);
    
            // Message-Handler fängt Nachichten ab und führt die vordefinierte Funktion aus
            BEGIN_MESSAGE_MAP
              // PARAMETER (Name der Msg, Messages::TMessage, Funktionsname)
              MESSAGE_HANDLER(MYWM_TrdTest, Messages::TMessage, TrdTestMsgHandler)
            END_MESSAGE_MAP(TForm)
    };
    //---------------------------------------------------------------------------
    extern PACKAGE TMain *Main;
    //---------------------------------------------------------------------------
    #endif
    
    /* ThreadTest.cpp - Thread Test Unit */
    
    //---------------------------------------------------------------------------
    #include <vcl.h>
    #pragma hdrstop
    //---------------------------------------------------------------------------
    #include "ThreadTest.h"
    #include "MainUnit.h"
    //---------------------------------------------------------------------------
    #pragma package(smart_init)
    //---------------------------------------------------------------------------
    DWORD Pause = 5;
    //---------------------------------------------------------------------------
    __fastcall TTrdTest::TTrdTest(HANDLE MainHandle, int ID)
    : TThread(False)
    {
      // Thread freigeben, wenn Terminated == true
      FreeOnTerminate = true;
    
      // globale Variable => Main->Handle übergeben
      ThreadHandle = MainHandle;
    
      // Thread ID
      TrdID = ID;
    }
    //---------------------------------------------------------------------------
    void __fastcall TTrdTest::Execute()
    {
      try
      {
        // Main Formular sagen, dass Thread AN ist
        SetThreadON();
    
        // Zähler
        int counter = 0;
    
        // Zeit
        int Zeit = ::GetTickCount();
    
        while (!Terminated)
        {
          counter++;
    
          if (::GetTickCount() - Zeit > 1000)
          {
            // Wert des Zählers an Main Formular senden
            SendCounter(counter);
    
            // Zähler reset
            counter = 0;
    
            // Zeit reset
            Zeit = ::GetTickCount();
          }
    
          Sleep(Pause);
        }
    
        // Main Formular sagen, dass Thread AUS ist
        SetThreadOFF();
      }
      catch (Exception *E)
      {
        SetThreadOFF();
    
        Application->MessageBox( String("Fehler in Thread: Trd " + String(TrdID)).c_str(), "debug:", 0+48 );
        ShowException(E, NULL);
      }
    }
    //---------------------------------------------------------------------------
    
    // ##########################################################################
    // Zähler Wert senden
    // ##########################################################################
    
    //---------------------------------------------------------------------------
    void __fastcall TTrdTest::SendCounter(int Wert)
    {
      SendMessage(ThreadHandle, MYWM_TrdTest, TrdID, ( Wert * (-1) ) );
    }
    //---------------------------------------------------------------------------
    
    // ##########################################################################
    // Thread Status AN / AUS
    // ##########################################################################
    
    //---------------------------------------------------------------------------
    void __fastcall TTrdTest::SetThreadON(void)
    {
      SendMessage(ThreadHandle, MYWM_TrdTest, TrdID, 1);
    }
    //---------------------------------------------------------------------------
    void __fastcall TTrdTest::SetThreadOFF(void)
    {
      SendMessage(ThreadHandle, MYWM_TrdTest, TrdID, 0);
    }
    //---------------------------------------------------------------------------
    
    /* ThreadTest.h - Thread Test Header */
    
    //---------------------------------------------------------------------------
    #ifndef ThreadTestH
    #define ThreadTestH
    //---------------------------------------------------------------------------
    #include <Classes.hpp>
    #include <Controls.hpp>
    #include <StdCtrls.hpp>
    //---------------------------------------------------------------------------
    // Windows Message für Kommunikation mit Main-Formular
    #define MYWM_TrdTest    (WM_APP + 1001)
    //---------------------------------------------------------------------------
    class TTrdTest : public TThread
    {
    private:
            //-------------------------------------------------------------------
            //--- Zähler Wert senden
            //-------------------------------------------------------------------
            void __fastcall SendCounter(int Wert);
            //-------------------------------------------------------------------
            //--- Thread Status AN / AUS
            //-------------------------------------------------------------------
            void __fastcall SetThreadON(void);
            void __fastcall SetThreadOFF(void);
    protected:
    	virtual void __fastcall Execute(void);
    public:
            __fastcall TTrdTest(HANDLE MainHandle, int ID);
    
            // Variable die im Gesamten Thread benutzt werden kann und
            // auch vom Hauptformular gelesen und verändert werden kann
            HANDLE ThreadHandle;
    
            // Thread ID
            int TrdID;
    };
    //---------------------------------------------------------------------------
    #endif
    

    ---
    Den Wert des Zählers sende ich notgedrungen 'negativ', weil ich nicht wusste
    wie ich sonst noch einen anderen Wert senden kann.
    [i](wobei natürlich ein 2. Message Handler möglich wäre)*

    -CppWannaBee

    p.s.: Ich hoffe jetzt meldet sich mal jemand! 🙄
    ... und bitte an meine 5 Fragen denken! 🙂



  • Hallo

    Frage 1: Ich habe den Header des Threads ins Main-Formular eingebunden und das Handle für den Thread als globale Variable ebenfalls im Main-Formular deklariert.
    => Gibt es eine andere (evtl. bessere) Variante wie ich das machen kann?

    Der Header-Include ist in Ordnung, statt ein globalen Variablen solltest du lieber eine Membervariablen des Forms nehmen.

    Frage 2: Wenn ich den Thread mit 'Terminate()' beende und ihn später erneut mit 'new' aufrufe, gibt es doch sicher i-wann einen Speicherüberlauf!?
    => Muss ich außer 'Terminate()' noch etwas machen um den Thread "sauber" zu beenden?
    ('delete' kann ich ja nicht nehmen, da ich sonst die globale Handle-Variable löschen würde 🙄 )

    Siehe die Eigenschaft TThread::FreeOnTerminate in der BCB-Hilfe.
    delete löscht keine Variablen, sondern nur Instanzen im Speicher. delete darfst du für Threads nur dann aufrufen wenn Terminate aufgerufen wurde, aber FreeOnTerminate auf false stand.

    Frage 3: Ich habe aus den Threads mit 'SendMessage' eine Nachricht an einen MessageHandler im Main-Formular geschickt, wenn ein Thread gestartet oder beendet wird.
    => Gibt es eine bessere Variante dem Main-Formular den Status der Threads mitzuteilen?

    Erweitere deine Thread-Klasse um einen Zeiger auf das Form, dann kannst du vom Thread auch direkt Methoden der Form-Instanz aufrufen. Aber bitte nur Synchronisiert. Weiteres dazu im Thread-Tutorial hier im Forum.

    Frage 4: Ich berechne in den Threads eine Variable und möchte diese dann ans Main-Formular schicken.
    => Wie kann ich die Variable aus den Threads ans Main-Formular schicken und diese dann anzeigen lassen?

    Siehe Antwort auf Frage 3.

    Frage 5: Wie bereits erwähnt, habe ich mir C++ in meiner Freizeit durch Beispiele und viel lesen selbst beigebracht (ohne Lehrgang etc.)
    => Gibt es an meinem Sourcecode sonst noch etwas was ich anders machen sollte?

    Da ich nicht überblicke, was du mit deinem Programm genau bezweckst, kann ich dazu erstmal nichts weiter als das schon geschriebene sagen.

    bis bald
    akari



  • Hallo akari! 🙂

    Schonmal vielen Dank für deine Antworten.

    akari schrieb:

    Der Header-Include ist in Ordnung, statt ein globalen Variablen solltest du lieber eine Membervariablen des Forms nehmen.

    Wie kann ich denn das Handle des Threads in meiner Klasse 'TMain' unterbringen?
    Ich includiere den Thread Header doch erst nach dem Main Header 🙄
    Wenn ich jetzt versuche ein Handle zu erstellen, kennt er 'TTrdTest' ja nicht...

    akari schrieb:

    Siehe die Eigenschaft TThread::FreeOnTerminate in der BCB-Hilfe.
    delete löscht keine Variablen, sondern nur Instanzen im Speicher. delete darfst du für Threads nur dann aufrufen wenn Terminate aufgerufen wurde, aber FreeOnTerminate auf false stand.

    Gut. Damit wäre diese Frage auf jeden Fall beantwortet.

    akari schrieb:

    Erweitere deine Thread-Klasse um einen Zeiger auf das Form, dann kannst du vom Thread auch direkt Methoden der Form-Instanz aufrufen. Aber bitte nur Synchronisiert. Weiteres dazu im Thread-Tutorial hier im Forum.

    Den Zeiger auf das Form, habe ich bereits durch den Include des Main-Form Headers. Mit 'Synchronize()' habe ich vorher auch schon gearbeitet.

    Könnte ich das mit 'Synchronize()' denn so lösen?:

    /* MainUnit.cpp - Main Form */
    
    ...
    void __fastcall TMain::TextAnzeigen(String Text)
    {
      Status->Lines->Add(Text);
    }
    ...
    
    /* ThreadTest.cpp - Thread Unit */
    
    // in der Execute()-Funktion
    ...
    Text = "Hallo Welt"; // String Text = Member Variable von TTrdTest
    Synchronize(SendText);
    ...
    
    void __fastcall TTrdTest::SendText(void)
    {
      Main->TextAnzeigen(Text);
    }
    ...
    

    Wäre das so denn richtig umgesetzt?

    akari schrieb:

    Da ich nicht überblicke, was du mit deinem Programm genau bezweckst, kann ich dazu erstmal nichts weiter als das schon geschriebene sagen.

    Es ging mir mehr darum, ob ich an meinem Programmier-Stil etwas verändern soll.
    Also ein Beispiel wären wohl globale Variablen, die ich wohl noch zu oft nehme.
    Richtig? 😃

    -CppWannaBee



  • Hallo

    CppWannaBee schrieb:

    Wie kann ich denn das Handle des Threads in meiner Klasse 'TMain' unterbringen?
    Ich includiere den Thread Header doch erst nach dem Main Header 🙄
    Wenn ich jetzt versuche ein Handle zu erstellen, kennt er 'TTrdTest' ja nicht...

    Stichwort Forward Declaration. Ich skizzire mal das Vorgehen in deinem konkretem Beispiel :

    // TMain.h
    
    class TTrdTest; // Forward Declaration hinzufügen. Kein Include!
    
    class TMain
    {
      ...
      TTrdTest *TrdTestHandle[10]; // Aus .cpp hierher verschoben
    };
    
    // TMain.cpp
    ...
    #include "TTrdTest.h"
    
    // Jetzt ist TTrdTest wie üblich verwendbar
    

    Den Zeiger auf das Form, habe ich bereits durch den Include des Main-Form Headers.

    Damit verwendest du aber wieder eine globale Variable, und davon solltest du wegkommen.

    Könnte ich das mit 'Synchronize()' denn so lösen?: ...

    Ja, so ist Synchronize anzuwenden.

    Es ging mir mehr darum, ob ich an meinem Programmier-Stil etwas verändern soll.
    Also ein Beispiel wären wohl globale Variablen, die ich wohl noch zu oft nehme.

    Da liese sich sicher noch ein paar Sachen verbessern, mir fehlt jetzt aber die Zeit um dein Quellcode nach konkreten Stellen zu durchsuchen. Wichtig sollte erstmal sein das der Quellcode fehlerfrei ist und du auch verstehst wie er funktioniert.

    bis bald
    akari



  • Hallo akari!

    akari schrieb:

    Stichwort Forward Declaration.

    Danke für diesen prima Tipp! 🙂 (... und das super Beispiel!)
    Ich hatte bisher noch nichts von Forwad Declaration gehört.

    akari schrieb:

    Damit verwendest du aber wieder eine globale Variable, und davon solltest du wegkommen.

    Da hast du Recht. Ich denke momentan leider noch in meinem alten Programmier-Schema. 🙄

    Ich habe jetzt probiert im Header des Threads einen Zeiger auf das Main Form
    zu erstllen. Da der Thread das Main Form auch (noch) nicht "kennt",
    habe ich das mit dem neu gelernten Forward Declaration gemacht:

    /* TrdTest.h - Thread Test Header */
    
    class TMain; // Forward-Deklaration des Main Form
    ...
    
    class TTrdTest : public TThread
    {
    private:
            // Zeiger auf Main Form
            TMain *MainForm;
    ...
    

    Wenn ich dann aber mit 'Synchronize()' eine Methode des Threads aufrufe,
    bekomme ich eine Zugriffsverletzung. 🙄

    /* TrdTest.cpp - Thread Test Unit */
    
    void __fastcall TTrdTest::Execute()
    {
    ...
    
        // Main Formular sagen, dass Thread AN ist
        Synchronize(SendThreadON);
    ...
    }
    
    void __fastcall TTrdTest::SendThreadON(void)
    {
      MainForm->ThreadStatus(TrdID, 1); // Thread AN
    
      // Main->ThreadStatus(TrdID, 1); /* <<< so funktioniert es         */
                                       /*     aber da ist Main ja global */
    }
    ...
    

    Wie muss ich denn den Zeiger deklarieren, damit es funktioniert?

    akari schrieb:

    Ja, so ist Synchronize anzuwenden.

    Klappt wirklich ohne Probleme. 🙂
    Mit dem 'Synchronize()' konnte ich jetzt endlich meine 'SendMessage()'-Methoden
    raushauen und alles mit beliebigen Parametern übergeben. 😉

    akari schrieb:

    Wichtig sollte erstmal sein das der Quellcode fehlerfrei ist und du auch verstehst wie er funktioniert.

    Das hast du gut gesagt. Ich möchte den Quellcode natürlich auch verstehen.
    Daher stelle ich hier auch meine Fragen. Danke für das gute Erklären! 🙂

    -CppWannaBee



  • Hast du MainForm denn schon Main zugewiesen? Weil sonst zeigt der Pointer ja noch ins Datennirgendwo 😉
    Zu Pointern siehe:
    http://www.youtube.com/watch?v=UvoHwFvAvQE

    mfg
    xXx



  • Hallo -=]xXx[=-!

    -=]xXx[=- schrieb:

    Hast du MainForm denn schon Main zugewiesen? Weil sonst zeigt der Pointer ja noch ins Datennirgendwo 😉

    Ach da lag das Problem. Jetzt wo ich mir das anschaue, springt mir der Fehler ja direkt ins Gesicht. 😃

    -=]xXx[=- schrieb:

    Zu Pointern siehe:
    http://www.youtube.com/watch?v=UvoHwFvAvQE

    Hehe, sehr gutes Video. Hat er wirklich gut erklärt. 😉

    Ich danke dir für deine schnelle Hilfe!

    -CppWannaBee



  • So... habe alles angepasst und das Beispiel-Projekt (siehe oben) komplett auf 'Synchronize()' umgestellt.

    /* MainUnit.cpp - Main Form Unit */
    
    //---------------------------------------------------------------------------
    #include <vcl.h>
    #pragma hdrstop
    //---------------------------------------------------------------------------
    #include "MainUnit.h"
    #include "ThreadTest.h"
    //---------------------------------------------------------------------------
    #pragma package(smart_init)
    #pragma resource "*.dfm"
    //---------------------------------------------------------------------------
    TMain *Main;
    //---------------------------------------------------------------------------
    __fastcall TMain::TMain(TComponent* Owner)
            : TForm(Owner)
    {
    }
    //---------------------------------------------------------------------------
    
    // ##########################################################################
    // Main Formular OnCreate Event
    // ##########################################################################
    
    //---------------------------------------------------------------------------
    void __fastcall TMain::FormCreate(TObject *Sender)
    {
      // Maximale Anzahl Threads
      TrdsMax = 50;
    }
    //---------------------------------------------------------------------------
    
    // ##########################################################################
    // Main Formular OnClose Event
    // ##########################################################################
    
    //---------------------------------------------------------------------------
    void __fastcall TMain::FormClose(TObject *Sender, TCloseAction &Action)
    {
      for (int i = 0; i < TrdsMax; ++i)
      {
        // Threads beenden
        if (TrdTestHandle)
        {
          TrdTestHandle[i]->Terminate();
          TrdTestHandle[i] = NULL;
        }
      }
    }
    //---------------------------------------------------------------------------
    
    // ##########################################################################
    // Buttons
    // ##########################################################################
    
    //---------------------------------------------------------------------------
    void __fastcall TMain::BtnStartClick(TObject *Sender)
    {
      Zeit = ::GetTickCount();
    
      BtnStart->Enabled = false;
    
      // Threads Aktiv = 0, Thread Anzahl = 10 (Max: 50)
      TrdsActive = 0, TrdCnt = 10;
    
      for (int i = 0; i < TrdCnt; ++i)
      {
        // Threads starten (ID des Threads)
        TrdTestHandle[i] = new TTrdTest(i+1);
      }
    }
    //---------------------------------------------------------------------------
    void __fastcall TMain::BtnStopClick(TObject *Sender)
    {
      Zeit = ::GetTickCount();
    
      BtnStop->Enabled = false;
    
      for (int i = 0; i < TrdCnt; ++i)
      {
        // Threads beenden
        if (TrdTestHandle[i])
        {
          TrdTestHandle[i]->Terminate();
          TrdTestHandle[i] = NULL;
        }
      }
    }
    //---------------------------------------------------------------------------
    
    // ##########################################################################
    // Thread Status - Public Methode - Aufruf nur aus den Threads!
    // ##########################################################################
    
    //---------------------------------------------------------------------------
    void __fastcall TMain::ThreadStatus(int TrdID, int Status)
    {
      if (Status == 1) // Thread AN
      {
        // Anzahl aktiver Threads
        TrdsActive++;
    
        // Status anzeigen
        AnzStatus->Lines->Add("Threads: " + String(TrdsActive) + " - TrdID: " + String(TrdID));
    
        if (TrdsActive == TrdCnt) // alle Threads sind AN
        {
          BtnStop->Enabled = true;
    
          AnzStatus->Lines->Add("---");
          AnzStatus->Lines->Add("Threads gestartet. (" + String(::GetTickCount() - Zeit) + " ms)");
          AnzStatus->Lines->Add("---");
    
          for (int i = 0; i < TrdCnt; ++i)
          {
            AnzStatus->Lines->Add("Thread Counter " + String(i) + ": 0");
          }
        }
      }
      else if (Status == 0) // Thread AUS
      {
        if (TrdsActive == TrdCnt)
        {
          AnzStatus->Lines->Add("---");
        }
    
        // Anzahl aktiver Threads
        TrdsActive--;
    
        // Status anzeigen
        AnzStatus->Lines->Add("Threads: " + String(TrdsActive) + " - TrdID: " + String(TrdID));
    
        if (TrdsActive == 0) // alle Threads sind AUS
        {
          BtnStart->Enabled = true;
    
          AnzStatus->Lines->Add("---");
          AnzStatus->Lines->Add("Threads gestoppt. (" + String(::GetTickCount() - Zeit) + " ms)");
          AnzStatus->Lines->Add("---");
        }
      }
    }
    //---------------------------------------------------------------------------
    
    // ##########################################################################
    // Thread Counter - Public Methode - Aufruf nur aus den Threads!
    // ##########################################################################
    
    //---------------------------------------------------------------------------
    void __fastcall TMain::ThreadCounter(int TrdID, int Counter)
    {
      // Wert des Zählers anzeigen (Wert = Durchläufe pro Sekunde)
      AnzStatus->Lines->Strings[(TrdCnt - 1) + 3 + TrdID] = "Thread Counter " + String(TrdID) + ": " + String(Counter);
    }
    //---------------------------------------------------------------------------
    
    // ##########################################################################
    // Thread ERROR - Public Methode - Aufruf nur aus den Threads!
    // ##########################################################################
    
    //---------------------------------------------------------------------------
    void __fastcall TMain::ThreadError(int TrdID, String Error)
    {
      AnzStatus->Lines->Add("---");
    
      // Error anzeigen
      AnzStatus->Lines->Add("Thread: " + String(TrdID) + " - Error: " + Error );
    
      AnzStatus->Lines->Add("---");
    }
    //---------------------------------------------------------------------------
    
    /* MainUnit.h - Main Form Header */
    
    //---------------------------------------------------------------------------
    #ifndef MainUnitH
    #define MainUnitH
    //---------------------------------------------------------------------------
    #include <Classes.hpp>
    #include <Controls.hpp>
    #include <StdCtrls.hpp>
    #include <Forms.hpp>
    //---------------------------------------------------------------------------
    class TTrdTest; // Forward-Deklaration des Threads
    //---------------------------------------------------------------------------
    class TMain : public TForm
    {
    __published:	// Von der IDE verwaltete Komponenten
            TButton *BtnStart;
            TButton *BtnStop;
            TMemo *AnzStatus;
            //-------------------------------------------------------------------
            //--- Main Formular OnCreate Event
            //-------------------------------------------------------------------
            void __fastcall FormCreate(TObject *Sender);
            //-------------------------------------------------------------------
            //--- Main Formular OnClose Event
            //-------------------------------------------------------------------
            void __fastcall FormClose(TObject *Sender, TCloseAction &Action);
            //-------------------------------------------------------------------
            //--- Buttons
            //-------------------------------------------------------------------
            void __fastcall BtnStartClick(TObject *Sender);
            void __fastcall BtnStopClick(TObject *Sender);
    //---------------------------------------------------------------------------
    private:	/* Variablen-Deklarationen */
            // Thread Handles
            TTrdTest *TrdTestHandle[50]; // maximal 50 Threads
    
            // Anzahl aktiver Threads, Threads maximal
            int TrdsActive, TrdCnt, TrdsMax;
    
            // Zeit Start - Ende
            int Zeit;
    //---------------------------------------------------------------------------
    public:		// Methoden-Deklarationen
            // Konstruktor
            __fastcall TMain(TComponent* Owner);
    
            // Thread AN / AUS - Aufruf nur aus den Threads!
            void __fastcall ThreadStatus(int TrdID, int Status);
    
            // Werte aus den Threads - Aufruf nur aus den Threads!
            void __fastcall ThreadCounter(int TrdID, int Counter);
    
            // Thread ERROR - Aufruf nur aus den Threads!
            void __fastcall ThreadError(int TrdID, String Error);
    };
    //---------------------------------------------------------------------------
    extern PACKAGE TMain *Main;
    //---------------------------------------------------------------------------
    #endif
    
    /* ThreadTest.cpp - Thread Test Unit */
    
    //---------------------------------------------------------------------------
    #include <vcl.h>
    #pragma hdrstop
    //---------------------------------------------------------------------------
    #include "ThreadTest.h"
    #include "MainUnit.h"
    //---------------------------------------------------------------------------
    #pragma package(smart_init)
    //---------------------------------------------------------------------------
    __fastcall TTrdTest::TTrdTest(int ID)
    : TThread(False)
    {
      // Pause
      Pause = 5; // 5 ms
    
      // Thread freigeben, wenn Terminated == true
      FreeOnTerminate = true;
    
      // Thread ID
      TrdID = ID;
    
      // Fehler im Thread
      Error = "";
    
      // Zeiger auf Main Form
      MainForm = Main;
    }
    //---------------------------------------------------------------------------
    void __fastcall TTrdTest::Execute()
    {
      try
      {
        // Main Formular sagen, dass Thread AN ist
        Synchronize(SendThreadON);
    
        // counter - Durchläufe pro Sekunde
        counter = 0;
    
        // Zeit
        int Zeit = ::GetTickCount();
    
        while (!Terminated)
        {
          counter++;
    
          if (::GetTickCount() - Zeit > 1000)
          {
            // Wert des Zählers an Main Formular senden
            Synchronize(SendCounter);
    
            // Zähler reset
            counter = 0;
    
            // Zeit reset
            Zeit = ::GetTickCount();
          }
    
          Sleep(Pause);
        }
    
        // Main Formular sagen, dass Thread AUS ist
        Synchronize(SendThreadOFF);
      }
      catch (Exception *E)
      {
        // Exception in String speichern
        Error = E->Message;
    
        // Main Formular sagen, dass in Thread ein Fehler aufgetreten ist
        Synchronize(SendThreadError);
    
        // Main Formular sagen, dass Thread AUS ist
        Synchronize(SendThreadOFF);
      }
    }
    //---------------------------------------------------------------------------
    
    // ##########################################################################
    // Zähler Wert senden
    // ##########################################################################
    
    //---------------------------------------------------------------------------
    void __fastcall TTrdTest::SendCounter(void)
    {
      MainForm->ThreadCounter(TrdID, counter);
    }
    //---------------------------------------------------------------------------
    
    // ##########################################################################
    // Thread Status AN / AUS
    // ##########################################################################
    
    //---------------------------------------------------------------------------
    void __fastcall TTrdTest::SendThreadON(void)
    {
      MainForm->ThreadStatus(TrdID, 1); // Thread AN
    }
    //---------------------------------------------------------------------------
    void __fastcall TTrdTest::SendThreadOFF(void)
    {
      MainForm->ThreadStatus(TrdID, 0); // Thread AUS
    }
    //---------------------------------------------------------------------------
    
    // ##########################################################################
    // Thread Status ERROR
    // ##########################################################################
    
    //---------------------------------------------------------------------------
    void __fastcall TTrdTest::SendThreadError(void)
    {
      MainForm->ThreadError(TrdID, Error); // Thread ERROR
    }
    //---------------------------------------------------------------------------
    
    /* ThreadTest.h - Thread Test Header */
    
    //---------------------------------------------------------------------------
    #ifndef ThreadTestH
    #define ThreadTestH
    //---------------------------------------------------------------------------
    #include <Classes.hpp>
    #include <Controls.hpp>
    #include <StdCtrls.hpp>
    //---------------------------------------------------------------------------
    class TMain; // Forward-Deklaration des Main Form
    //---------------------------------------------------------------------------
    class TTrdTest : public TThread
    {
    private:
            // Pause
            DWORD Pause;
    
            // Thread ID
            int TrdID;
    
            // counter - Durchläufe pro Sekunde
            int counter;
    
            // Fehler im Thread
            String Error;
    
            // Zeiger auf Main Form
            TMain *MainForm;
    
            //-------------------------------------------------------------------
            //--- Zähler Wert senden
            //-------------------------------------------------------------------
            void __fastcall SendCounter(void);
            //-------------------------------------------------------------------
            //--- Thread Status AN / AUS
            //-------------------------------------------------------------------
            void __fastcall SendThreadON(void);
            void __fastcall SendThreadOFF(void);
            //-------------------------------------------------------------------
            //--- Thread Status ERROR
            //-------------------------------------------------------------------
            void __fastcall SendThreadError(void);
    //---------------------------------------------------------------------------
    protected:
    	virtual void __fastcall Execute(void);
    //---------------------------------------------------------------------------
    public:
            __fastcall TTrdTest(int ID);
    };
    //---------------------------------------------------------------------------
    #endif
    

    Ein paar kleine Fragen habe ich allerdings noch. 😃

    Frage 1: Sollte ich den Wert der Member-Variable [i](int TrdsMax)* im Konstruktor von TMain setzen oder so wie bei mir im 'OnCreate'?
    => Welchen Unterschied macht das dann?

    Frage 2: Sagen wir mal der Thread beendet die Execute()-Methode mal wegen eines Fehlers.
    Dann wird dieser Fehler mit 'catch()' abgefangen.
    => Wird der Thread automatisch "terminiert" (Terminated == true), wenn er die Execute()-Methode verlässt?
    => Oder muss ich im 'catch()' noch 'Terminate()' schreiben?

    ---
    Ich möchte nochmal akari für seine kompetente und ausführliche Hilfe danken!
    Dank geht natürlich auch an -=]xXx[=- für den kleinen Denkanstoß! 😃

    -CppWannaBee



  • Hallo

    1. Im Konstruktor. Sowohl OnCreate als auch OnDestroy solltem im C++ Builder nicht verwendet werden, das sind Überbleibsel aus der Delphi-Herkunft. begründung findest du mit der Forumssuche.

    2. Ob bei einer Exception der Thread noch automatisch gelöscht wird, weiß ich jetzt nicht genau. Solange es aber nur um das äußerste catch geht, ist das kein Problem, da mit dem Verlassen vom Programm sowieso aller Speicher automatisch freigegeben wird.

    3. Bitte Poste keine ewig langen Quellcode-Auszüge, nur zur Fragestellung relevante Stellen. Wenn du ein ganzes Projekt verfügbar machen willst, dann wie schon gemacht über einen externen Anbieter für Dateiaustausch.

    bis bald
    akari



  • Hallo akari!

    akari schrieb:

    1. Im Konstruktor. Sowohl OnCreate als auch OnDestroy solltem im C++ Builder nicht verwendet werden, das sind Überbleibsel aus der Delphi-Herkunft. begründung findest du mit der Forumssuche.

    Danke für den Hinweis. Werde es ab sofort immer beachten.
    Hier der Link: Don't use OnCreate and OnDestroy, use C++ constructors and destructors instead

    akari schrieb:

    2. Ob bei einer Exception der Thread noch automatisch gelöscht wird, weiß ich jetzt nicht genau. Solange es aber nur um das äußerste catch geht, ist das kein Problem, da mit dem Verlassen vom Programm sowieso aller Speicher automatisch freigegeben wird.

    Also ich bin ja der Meinung, dass der Thread nicht automatisch durch das Verlassen der 'Execute()'-Methode terminiert wird.
    => Das werd ich mal ausprobieren mit 'OnTerminate' 🙄

    akari schrieb:

    3. Bitte Poste keine ewig langen Quellcode-Auszüge, nur zur Fragestellung relevante Stellen. Wenn du ein ganzes Projekt verfügbar machen willst, dann wie schon gemacht über einen externen Anbieter für Dateiaustausch.

    Ok, tut mir Leid.
    Ich war nur der Meinung das der gesamte Source mehr auf eventuelle Fehler hinweisen kann.
    Sollte ich mal wieder nen längeren Source haben, werd ich es über Free-FileHoster machen. 😉

    ---
    Nochmnals vielen Dank für deine Hilfe, akari!

    -CppWannaBee


Anmelden zum Antworten