Restart eines Threads



  • Folgendes Problem:
    Ich habe einen Hauptthread, der öffnet auf KLick einen weiteren Thread indem
    ein einfacher Counter ist. Nun ist es möglich diesen Counter-Thread zu suspenden und zu resumen, alles ohne Probleme. Auch Terminaten lässt er sich. Nur ist das Problem, dass ich den Counter auf KLick gerne restarten würde. d.h. bei KLick sollte er den Thread neu starten. Oder den Counter und wieder bei Null anfangen zu zählen.

    Unit1
    
    #include <vcl.h>
    #pragma hdrstop
    
    #include "Unit1.h"
    #include "Unit2.h"
    //---------------------------------------------------------------------------
    #pragma package(smart_init)
    #pragma resource "*.dfm"
    TForm1 *Form1;
    TThread *MyThread = new TMyThread(true);
    //---------------------------------------------------------------------------
    __fastcall TForm1::TForm1(TComponent* Owner)
      : TForm(Owner)
    {
    }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::Button1Click(TObject *Sender)
    {
      if(!MyThread)                       // wenn der CounterThread nicht 
                                          //existiert, dann neuen erzeugen
      {
        TThread *MyThread= new TMyThread(false);
      }
      else MyThread->Resume();
    }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::Button2Click(TObject *Sender)
    {
     MyThread->Terminate();    //sendet Terminate Signal
    }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::Button3Click(TObject *Sender)
    {
      MyThread->Resume();        //setzt Counter fort
    }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::Button4Click(TObject *Sender)
    {
      MyThread->Suspend();        //stoppt counter
    }
    //---------------------------------------------------------------------------
    
    Unit2
    
    #include <vcl.h>
    #pragma hdrstop
    
    #include "Unit2.h"
    #include "Unit1.h"
    #pragma package(smart_init)
    //---------------------------------------------------------------------------
    
    //   Wichtig: Methoden und Eigenschaften von Objekten der VCL können nur
    //   in Methoden verwendet werden, die Synchronize aufrufen, z.B.:
    //
    //      Synchronize(UpdateCaption);
    //
    //   wobei UpdateCaption so aussehen könnte:
    //
    //      void __fastcall TMyThread::UpdateCaption()
    //      {
    //        Form1->Caption = "In Thread aktualisiert";
    //      }
    //---------------------------------------------------------------------------
    
    __fastcall TMyThread::TMyThread(bool CreateSuspended)
      : TThread(CreateSuspended)
    {
      FreeOnTerminate=true;
    }
    //---------------------------------------------------------------------------
    void __fastcall TMyThread::Execute()
    {
      while(Terminated==false)    //solange kein Terminate Signal empfangen wurde
      {                           // Funktion Counter
        Counter();
      }
      FreeOnTerminate=true;
    }
    //---------------------------------------------------------------------------
    void TMyThread::Counter()
    {
      for(int i=0; i<20; i++)        // einfache Zählschleife
      {
        Form1->RichEdit1->Lines->Text=i;
        Sleep(100);
      }
     return;
    }
    //---------------------------------------------------------------------------
    

    Bin für gute Tipps dankbar. Habe schon versucht den Thread zu löschen und neu zu starten, bekomme aber immer Fehler.

    Danke schon im Voraus

    PS: Macht weiter so habe bei euch schon einiges brauchbares gefunden....



  • Knuddelbärchen schrieb:

    bekomme aber immer Fehler

    Ich bekomme auch immer Fehler, kannst du mir vielleicht sagen, woran das liegt?
    Was sagst du? Dazu müsstets du zumindest wissen, wie die Fehler lauten!?

    😉



  • Also Knuddelbärchen,

    um Deinen Thread zu beenden, must Du Temrminated auf true setzen.
    Wenn der Thread terminiert ist, kannst Du einen neuen Thread erzeugen. Möchtest Du aber lediglich Deinen Counter auf einen definierten Anfangswert setzen, definiere diesen als Membervariable in Deiner Threadklasse. Diese kannst Du dann beliebig von 'außen ' mit einer entspechenden Zugriffsmethode ändern.

    void TMyThread::Counter() 
    { 
        do
        {  
           Form1->RichEdit1->Lines->Text=nCount++; 
           Sleep(100);
        }while (20 > nCount) 
    }
    

    Aber Achtung:
    Diese Änderung von 'außen' nur machen, wenn der Thread suspended, also in Pause ist, oder mit Hilfe einer Criticalsection verhindern, das beide Threads gleichzeitig auf die Variable nCount zugreifen.

    Gruß
    Gerhard

    P.S.
    Das FreeOnTerminate in der Execute Methode kannst Du weglassen:

    //--------------------------------------------------------------------------- 
    void __fastcall TMyThread::Execute() 
    { 
      while(!Terminated)    //solange kein Terminate Signal empfangen wurde 
      {                           // Funktion Counter 
        Counter(); 
        nCount = 0;        // Counter wieder auf Anfangswert NULL setzen
      } 
    } 
    //---------------------------------------------------------------------------
    


  • @ Jansen

    Folgendes Problem: 1 TForm mit 4 Buttons,
    1 Start
    2 MyThread->Terminate();
    3 MyThread->Resume();
    4 MyThread->Suspend();

    1 3 und 4 gehen gut. Anhalten und fortsetzen ist kein Problem.

    Button 3

    MyThread->Terminate();
    

    Sendet das Terminate Signal an den Thread. Dieser hält nach durchlaufen des Counters auch wunschgemäß an. Soweit so gut. Nun wollte ich den Counter wieder
    mit Button 1 starten:

    if(!MyThread)
            {
               TThread *MyThread= new TMyThread(false);
            }
            else MyThread->Resume();
    

    Wenn der Thread terminiert ist müsste doch eigentlich ein neuer Thread erzeugt werden. Sollte der alte noch vorhanden sein, was eigentlich nicht so sein dürfte, sollte er fortgesetzt werden.
    Jedoch bekomme ich beim Drücken des Button 1,2,3 eine Fehlermeldung:
    ---------------------------
    Debugger Exception Notification
    ---------------------------

    Project Project1.exe raised exception class EThread with message 'Thread Error: Das Handle ist ungültig (6)'. Process stopped. Use Step or Run to continue.

    Aber ich verstehe im Moment nicht warum. Wende ich den Terminate Befehl falsch an ??

    @ Gerd
    Die Idee mit dem Counter ist net schlecht. Thx.

    Aber wie setze ich Terminated auf True ??
    MyThread->Terminate(); (dachte ich wenigstens)
    Fehlt da noch was, er wird ja angehalten. Aber er lässt sich nicht neu initialisieren. 😞



  • Jetzt ist mir gerade noch was aufgefallen: Wenn ich FreeOnTerminaste auf True setze kriege ich beim erneuten Start eine Fehlermeldung. Setze ich den Wert auf false, kann ich den Start-Button drücken. Es geschieht zwar nichts aber er stürzt auch nicht ab. Kann es sein, dass der Thread zwar terminiert worden ist, aber noch irgendwo zu finden ist ?? Oder initialisiere ich den Thread vielleicht falsch ??
    Ich mache es mit dem BCB. Zuerst ne Anwendung und dann ein Thread-Objekt hinzufügen.

    CU GF



  • Hi,
    du erzeugst einen neuen thread nur, wenn !MyThread also wenn MyThread == NULL.
    Zwar ist FreeOnTerminate = true, aber es kann sein dass trotzdem nicht NULL in der Variablen steht. Vielleicht nutzt es dir was wenn du die Variable nach dem beenden des Threads auf NULL setzt. Vielleicht gibt es auch noch bessere Wege
    CU



  • if(MyThread!=NULL)
      {
        Application->MessageBoxA("Fehler","Error",MB_OK);
        return;
      }
    

    Und tatsächlich erscheint bei erneutem Startversuch die Messagebox. d.h. es muss noch irgendwo irgendwas drin stehen.

    @ rincewind
    Die Idee ist gut.

    Habe jetzt MyThread=NULL gesetzt, also direkt nach dem Terminate Befehl. Jetzt lässt sich der Start Button wieder drücken und erzeugt einen neuen Thread. Ohne Fehlermeldung.
    Allerdings funktionieren die Buttons Suspend und Resume nach dem Neustart nicht mehr. Vermutlich steht da noch ein Zeiger auf den alten Thread drin.
    Hat jemand ne Idee wie ich diese Adressen rausbekomme bzw. aktualisieren kann, damit der neu erzeugte Thread wieder suspendet und resumed werden kann.
    Thx

    Habe jetzt gerade festgestellt, dass die Idee mit dem NUll setzen jedoch nur beim ersten Mal funktioniert. Beim zweiten ´Versuch den Thread zu unterbrechen wird wieder ene Fehlermeldung erzeugt und weiterhin wird nun für jedes Drücken des STartButtons auf einmal ein neuer Thread erzeugt.
    HILFE !!!!!



  • Hi,
    kannst dir ja auch mal das Thread-Beispiel vom BCB anschauen....eigene OnTerminate Behandlung schreiben, flag setzen und den neustart davon abhängig machen



  • Vielleicht ein paar Grundlagen zum besseren Verständnis:

    Ein Thread läuft innerhalb der Execute-Methode, wobei und das ist z.B. für Datenbankanbindungen wichtig, diese Execute-Methode in einem anderen Speicherbereich angesiedelt ist als der Main-Thread.

    void __fastcall My_TThread::Execute()
    {
        MyFunction();
    }
    

    In diesem Fall wird MyFunction einmal abgearbeitet und der Thread dann beendet.
    Möchte ich, dass der Thread mehr als einmal MyFunction() aufruft, wird das ganze z.B. in eine while-Schleife gepackt.

    void __fastcall My_TThread::Execute()
    {
         while (!Terminated) 
            MyFunction();
    
    }
    

    Nun läuft der Thread ständig bis das Flag Terminated auf true gesetzt wird (My_Thread->Terminate())
    Möchte ich, dass der Thread genau einmal MyFunction() aufruft und dann nicht! beendet sondern sich schlafen legt(Suspend), bis er wieder aufgeweckt wird (Resume) um dann erneut MyFunction() aufruft, sieht das hanze so aus:

    void __fastcall My_TThread::Execute()
    {
         while (!Terminated) 
        {
            MyFunction();
            Suspend();
        }
    }
    

    Hier kann man erst einaml erkennen, dass Terminate nichts anderes als ein Flag ist, welches mit der Methode Terminate() lediglich auf true gesetzt wird. D.h. aber auch, wenn der Thread schläft (suspended ist) passiert gar nichts. Also vor einem Terminat prüfen, ob der Thread auch läuft. Das Flag Terminated kann man bzw. sollte man in der Ausführungsmethode (hier MyFunction) innerhalb von langen while/for Schleifen immer abfragen um diese ggf. eher verlassen zu können.
    Vorausgesetzt der Thread wurde suspended erzeugt (CreateSuspended = true)
    Was, bis auf wenige Ausnahmen eigentlich immer gemacht werden sollte.
    1.) Thread aufwecken (starten). MyThread sollte in der Mainklasse (MainForm) unter private: deklariert sein und im Konstrukor auf NULL gesetzt werden

    // Header von MainForm
        private:
           TThread *MyThread 
    
    // Konstruktor der MainForm
    __fastcall TMainForm::TMainForm(TComponent* Owner)
    	: TForm(Owner)
    {
        MyThread = NULL;
    }
    

    Mit Button 1 wird der Thread ggf. neu erzeugt und gestartet
    Button_1

    if (!MyTThread)
        //es gibt noch kein ThreadObjekt also erzeuge eines
            MyThread= new TMyThread(true); 
        MyThread->Resume(); //starte den Thread
    

    2.)Thread soll beendet werden. Ich bevorzuge immer FreeOnTerminate auf false zu setzen, deshalb räume ich das Thread-Objekt selbe auf.
    Button_2

    if (MyTThread)
        {
           MyTThread->Terminate(); // setze zuerst das Flag Terminated auf true
           if (MyTThread->Suspended) // schäft mein Thread ?
               MyTThread->->Resume(); // ja, wache ihn auf, damit er sich beenden kann
    
           thread_ShowElements->WaitFor(); // warte bis thread beendet
           delete MyTThread;    // loesche das ThreadObjekt
           MyTThread = NULL;    // sollte man mit jedem geloeschten Objekt machen
        }
    

    Du kannst nun mit Button 2 den Thread beenden und mit Button 1 neu erzeugen. Um aber nur den Counter auf einen definierten Anfangswert zu setzen reicht es, wenn Du den Thread mit z.B. Button 3 auf Suspend setzt, den Counter auf einen bestimmten Wert setzt und dann mit Button_1 den Thread wieder startest

    Gruß
    Gerhard



  • Da ich einfach aus den Beispielen der werten "Frager" kopiert habe ist mir gerade beim nochmaligen Lesen aufgefallen, dass in diesen Beispielen die Thread-Klasse direkt von TThread abgeleitet wird, was nicht geht.
    Die Klasse TThread ist eine abstrakte Klasse von der man kein Threadobjekt direkt erzeugen kann. Also immer seine eigene Threadklasse von TThread ableiten:

    // im Header File
    class My_Thread : public TThread
    {
    }
    
    // Konstruktor im cpp File
    __fastcall My_Thread ::My_Thread (bool CreateSuspended)
                                       : TThread(CreateSuspended)
    {
    }
    

    Dieses bitte bei meinen vorherigen Ausführungen berücksichtigen bzw. im Header von MainForm verbessern.

    // Header von MainForm
        private:
          My_Thread * MyThread;
    

    Sorry !

    Gerhard



  • @ Gerhard

    Danke für deine Erklärungen, die waren echt gut. Auf jeden Fall läuft jetzt alles so wie gewollt.
    Wenn ich mir jedoch deinen und meinen Quelltext so ankucke ist er nicht sehr verschieden. Bis auf ein paar kleine Änderungen. z.B. die Sache mit dem eigenen CleanUpText von dir. Naja und vor allem die KLassenableitung. 😞

    Habe noch 2 Fragen:
    1.) Könnten die ersten Fehler davon kommen, dass ich Keineeigene Threadklasse abgeleitet habe ??
    2.) Ist es richtig, das der Thread bzw. die Adresse, nicht leer ist, obwohl er auf Terminated=true gesetzt wurde und FreeOnTerminate=true; was steht da noch drin ?? 😕

    Danke an Alle
    especially Gerhard 👍

    FGGF



  • hallo,

    die ausführungen von gerhardt waren schon mal recht sinnvoll und haben das programm sehr verbessert. hier kommt nun nun noch ein tip von mir: wie gerhard vorher schon erwähnte läuft ein thread in einem anderen bereich als der vcl-hauptthread. wenn man innerhalb eines threads nun auf vcl-objekte zugreift, kann das verheerende (kann muß aber nicht sofort) folgen haben. folgender code ist unsauberer programmierstil und sollte geändert werden:

    void __fastcall TMyThread::Execute() 
    { 
      while(Terminated==false)    //solange kein Terminate Signal empfangen wurde 
      {                           // Funktion Counter 
        Counter(); 
      } 
      FreeOnTerminate=true; 
    } 
    //--------------------------------------------------------------------------- 
    void TMyThread::Counter() 
    { 
      for(int i=0; i<20; i++)        // einfache Zählschleife 
      { 
        Form1->RichEdit1->Lines->Text=i; 
        Sleep(100); 
      } 
     return; 
    } 
    //---------------------------------------------------------------------------
    

    dieser code sollte folgendermaßen abgeändert werden:

    void __fastcall TMyThread::Execute() 
    { 
      FreeOnTerminate=true; //damit sogleich gesetzt und gültig.
      while(!Terminated)    
      {                               
        Synchronize(Counter); //Läuft nun mit dem vcl-hauptthread und ist ungefährlich.
      }  
    } 
    //--------------------------------------------------------------------------- 
    void TMyThread::Counter() 
    { 
      for(int i=0; i<20; i++)        // einfache Zählschleife 
      { 
        Form1->RichEdit1->Lines->Text=i; 
        Sleep(100); 
      } 
     return; 
    } 
    //---------------------------------------------------------------------------
    

    die Methode die man Synchronize übergibt läuft im context des vcl-mainthreads...

    mfg
    murph



  • Hab noch ne bessere Möglichkeit gefunden, ohne den Counter auf Null zu setzen.
    Einfach mit ner guten Implementierung der Schleife. Die jedesmal abfragt ob Terminate true ist oder net.
    Für alle dies interessiert. 2 Buttons
    Button1:

    if (!MyThread)
        //es gibt noch kein ThreadObjekt also erzeuge eines
            MyThread= new My_Thread(true);
          MyThread->Resume(); //starte den Thread
    

    Button2

    if (MyThread)
          {
             MyThread->Suspend();
             MyThread->Terminate(); // setze zuerst das Flag Terminated auf true
             if (MyThread->Suspended) // schäft mein Thread ?
                 MyThread->Resume(); // ja, wache ihn auf, damit er sich beenden kann
    
            MyThread->WaitFor(); // warte bis thread beendet
            delete MyThread;    // loesche das ThreadObjekt
            MyThread = NULL;    // sollte man mit jedem geloeschten Objekt machen
    

    und die Schleife die jedesmal auf Wahrheitsgehalt abgefragt wird

    for(i = 600; i > 0 ; i--)
      {
        if(!Terminated)
        {
          Form3->LETimer->Text=i;
          Sleep(1000);
        }
      else break;
      }
    

    Special Thx an Gerhard und Murphy. 😉

    FG GF 😮



  • Zu Deinen 2 Fragen:

    Habe noch 2 Fragen:
    1.) Könnten die ersten Fehler davon kommen, dass ich Keine eigene Threadklasse abgeleitet habe ??
    --->ja

    2.) Ist es richtig, das der Thread bzw. die Adresse, nicht leer ist, obwohl er auf Terminated=true gesetzt wurde und FreeOnTerminate=true; was steht da noch drin ??
    --->nein die Adresse ist nicht leer. Normalerweise steht dort immer noch die Adresse Deines Threads drin, der aber nicht mehr existiert und somit der Zeiger ins Nirvana zeigt.
    Deshalb sollte man auch alle Objekte die man mit new erzeugt hat und irgendwann gelöscht hat auf NULL setzen.
    Bsp.:

    int *i = new int;
    delete i;
    

    Sollte man in umfangreichen Programmen vergessen haben, dass man ein Objekt schon gelöscht hat, hat ein Zugriff ein erneuter Zugriff auf das Objekt (hier i) u.U. katastrophale Folgen. Bei einem erneuten löschen delelte i, wird man in der Regel mit einer Access vioaltion davon kommen.

    Gruß
    Gerhard



  • Gerhard schrieb:

    int *i = new int;
    delete i;
    

    Besser:

    int *i = new int;
    delete i;
    i = NULL;
    

    -junix


Anmelden zum Antworten