while abbrechen in Objekt orientierter Programmierung



  • Zumal TThread eine Methode zum Synchronisieren mit dem GUI-Main Thread mitbringt. Damit lässt sich die GUI auf die Schnelle aus dem Thread aktualisieren, ohne dass man was eigenes mit Locks bauen müsste.



  • Hi Eric,

    im Builder gibts die Funktion
    Application->ProcessMessages();
    Die sorgt dafür das Nutzereingaben ausgewertet werden. Also in der onclick-Routine des Buttons eine Schaltervariable setzen und in der Schleife abfragen ob die Schalterwariable schon gesetzt wurde. Dies kann ganz einfach in jeder while-Bedingung... geschehen.
    Nicht vergessen nach dem Abarbeiten die Schaltervariable ggf zuurückzusetzen.

    Gruß Mümmel



  • @mgaeckler sagte in while abbrechen in Objekt orientierter Programmierung:

    @hustbaer Wenn es genügt, daß TThread::Execute() das Terminate-Flag auswertet, kann man das schon so machen.

    Klar kann man. Ich wollte halt anmerken dass Cooperative Thread Cancellation über Boardmittel von Frameworks im Allgemeinen eine schlechte Idee ist. In dem Sinn dass es Probleme gibt sobald es als "das" Mittel der Wahl angesehen und von vielen Komponenten verwendet wird.

    Thirdparty-Libs werden i.d.R. von TThread eher nichts kennen, außer sie sind speziell für die VCL implementiert.

    Soll ja vorkommen dass jemand Libraries/Komponenten entwickelt die auf Frameworks ala VCL aufsetzen. Mir ist beim Suchen von passenden fertigen Libs/Komponenten öfter mal ein Projekt untergekommen das gut gepasst hätte aber dummerweise auf die VCL aufbaut. (Dummerweise für mich da ich die VCL eben nicht verwende.)



  • Hallo zusammen.

    Den Vorschlag von @Th69 die while Schleife mit "Terminated" zu beenden habe ich auch in meinem Testprogramm so gemacht. Das habe ich auch verstanden wie es funktioniert und stellt auch für mich die beste Methode dar, den Thread dann zu stoppen bzw. beenden.
    Aber es gibt da ja auch noch Variablen die im Hauptthread (Hauptprogramm) deklariert wurden und Objekte die im Hauptthread angelegt wurden.
    Da kommt die Antwort von @muemmel mit "Application->ProzessMessages();" schon gut. Aber soweit ich das verstanden habe, werden hier nur die eintretenden Ereignisse aus dem Hauptthread (Hauptprogramm) abgefangen und könnten im Thread verarbeitet werden. Hmmm das ist schon gut zu wissen, wie ich z.B. einen Tastendruck oder Mouseklick erkennen kann. Deshalb Danke für den Hinweis. Aber das löst leider das eigentliche Problem noch nicht.
    Deshalb habe ich mir die Antwort von @DocShoe mit dem "Synchronize();" angesehen. Ich muss gestehen, auf der Seite
    "http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/!!OVERLOADED_Synchronize_Classes_TThread.html"
    Gibt es zwar Info darüber, aber da blicke ich im Moment überhaupt nicht durch. Wann und wo und wie nutzt man das Synchronize? In meinem schlauen Buch "C++ Builder das Kompentium" steht dieses da auch nicht beschrieben. Lediglich, das man Synchronize nicht aus dem Hauptthread (Hauptprogramm) aufrufen soll, da dieses zu Endlosschleifen führen kann. Es wäre schön, wenn mal jemand hierzu ein kleines einfaches Beispiel geben könnte.

    Aber einen Teil meiner zweiten Frage habe ich bereits selber herausfinden können. Wie man die Objekte aus dem Hauptthread in den eigentlichen Thread mit übernehmen kann.
    Es war natürlich mein Fehler... denn ich habe in den Thread einfach das selbe eingegeben wie im Hauptthread auch.

    Label1->Caption = "Thread running";
    

    Richtig muss es aber so gemacht werden...

    Form1->Label1-Caption = "Thread running";
    

    Dann funktionierst auch! Ist mir auch jetzt klar. Ein Thread ist halt in der selben Ebene angesiedelt, wie das Hauptprogramm auch und nicht ein Teil des Hauptprogramms, welches unter "Form1" seinen Dienst tut.

    Aber, da wäre jetzt nur noch eine Sache. Das mit den Variablen. Wenn ich z.B. im Hauptprogramm eine unsigned char- Variable namens "SendByte" erzeugt habe, und diese im Thread z.B. noch verändert werden muss, dann kennt der Thread diese Variable nicht. Ich kann aber auch nicht schreiben...

    Form1->SendByte = SendByte + Bit5; //Bit5 = Wert, um das Bit 5 in SendByte zu setzen!
    

    Jetzt stellt sich die Frage, wie kann ich die Variable "SendByte" für den Thread sichtbar bzw. zugänglich machen?
    Wenn mir jemand sagen könnte wie man das macht, würde ich schon weiter kommen.
    ODER! kommt hierbei das "Synchronize();" zum Einsatz was @DocShoe schon erwähnte?
    Danke allen die sich hier doch sehr hilfreich beteiligen.
    Gruß Eric



  • Application->ProcessMessages() ist ein wahrscheinlicher Grund für plötzlich auftretende Schutzverletzungen und seltsames Verhalten. Solange man nicht genau weiß, wann und warum man das benutzt sollte man es nicht benutzen. Wenn´s irgendwie anders geht: Mach´s anders!



  • Multithreading ist kein einfaches Thema, obwohl´s erst ein Mal einfach aussieht.

    1. du darfst aus einem nebenläufigen Thread keine GUI Elemente aktualisieren. Das kann funktionieren, muss aber nicht.
    2. du musst gemeinsam benutzte Daten die Zugriffe gegeneinander verriegeln (nur wenn min. ein Schreibzugriff dabei ist)

    Zu 1)
    Dazu benutzt du die Synchronize() Methode von TThread.

    Zu 2)
    Der Zugriff auf die Daten muss per CriticalSection oder Mutex geschützt werden. Vor dem Zugriff rufst du Enter auf, wenn du damit fertig bist Leave.

    Das Ganze könnte in etwa so aussehen (C++98, keine Ahnung, ob du einen C++ Builder hast, der C++11 unterstützt). Alles primitiv und ohne RAII, da ist noch Luft nach oben.

    // mydata.h / mydata.cpp

    struct MyData
    {
    ....
    };
    

    MyThread.h

    #include "mydata.h"
    // fehlende Includes einfügen
    
    // Signatur für Callback
    typedef void (__closure* CallbackSig)( MyData& data );
    
    class MyThread : public TThread
    {
       MyData Data_;
       TCriticalSection* CriticalSection_;
    
    public:
       CallbackSig   OnDataChanged = nullptr;
    
    public:
       __fastcall MyThread();
       __fastcall ~MyThread();
    
       MyData& data();
       void lock();
       void release();
    
    private:
       void __fastcall Execute();
       void fire_data_changed() const;
    };
    

    MyThread.cpp

    #include "mythread.h"
    
    __fastcall MyThread::MyThread  :
       TThread( true ), // CreateSuspended
       CriticalSection_( new TCriticalSection() )
    {
       // Thread soll sich selbst löschen, wenn er die Execute-Methode verlässt
       AutoDelete = true;
       ...
    }
       
    __fastcall MyThread::~MyThread  
    {
       delete CriticalSection_;
    }
    
    void MyThread::lock()
    {
       CriticalSection_->Enter();
    }
    
    void MyThread::release()
    {
       CriticalSection_->Leave();
    }
    
    MyData& MyThread::data()
    {
       return Data_;
    }
    
    void __fastcall MyThread::Execute()
    {
        while( !Terminated )
        {
           // magic happens here
    
           // Zugriff auf Daten sichern
           lock();
           update_data();
           release();
    
           // in den Hauptthread der Anwendung wechseln und dann die Methode fire_data_changed aufrufen.
           // der aktuelle Thread wartet so lange, bis der fire_data_changed Aufruf beendet ist.
           Synchronize( fire_data_changed );  
        }
    }
    
    void MyThread::fire_data_changed() const
    {
       // Weil diese Methode im Hauptthread der Anwendung aufgerufen wird und der Thread, der
       // die Daten verändert, gerade (quasi) angehalten ist darf auf die Daten ohne Locking zuge-
       // griffen werden (der schreibende Thread schreibt im Moment garantiert nicht!)
       if( OnDataChanged )
       {
          OnDataChanged( Data_ );
       }
    }
    

    MyForm.h

    #include "mydata.h"
    #include "mythread.h"
    
    class MyForm1 : public TForm
    {
       MyThread* WorkerThread_;
    public:
       __fastcall MyForm1( TComponent* Owner );
    
    private:
       void show_data( const MyData& data );
       void on_data_changed( const MyData& data );
       void __fastcall on_timer( TObject* Sender );
    };
    

    MyForm.cpp

    #include "MyForm.h"
    
    __fastcall MyForm1::MyForm1( TComponent* Owner ) :
       TForm( Owner ),
       WorkerThread_( new MyThread() )
    {
       // Callback registrieren. Wenn der Thread die Daten verändert hat ruft er den Callback auf
       WorkerThread_->OnDataChanged = on_data_changed;
    
       // Thread starten
       WorkerThread_->Resume();
    }
    
    void MyForm1::on_data_changed( const MyData& data )
    {
       // der Aufruf findet im Hauptthread der Anwendung statt, daher dürfen hier GUI Elemente
       // verändert werden.
       show_data( data );
    }
    
    void __fastcall MyForm1::on_timer( TObject* sender  )
    {
       // der Zugriff auf die Daten muss synchronisert werden, da der Thread sie möglicherweise
       // in der Execute-Methode gerade verändert.
       WorkerThread_->lock();
       show_data( WorkerThread_->data() );
       WorkerThread_->release();
    }
    
    void MyForm::show_data( const MyData& data )
    {
       ...
    }
    


  • Hallo @DocShoe
    Woooouuuhh... das ist ganz klasse das du mich mit einem Beispielcode unterstützt. Ich muss mir das Ganze jetzt aber erst einmal genauer ansehen. Denn wie du schon geschrieben hast, sieht es einfach aus, ist es aber nicht.
    Der Ganze Aufwand hier, auch mit eurer Hilfe, betreibe ich ja nur, um einen Button aktiv zu halten, der eine Schleife stoppen soll... Ist schon ganz schön großer Aufwand, was man alles neues erlernen und ausprobieren muss um ein eigentlich kleines Ziel zu erreichen. Aber so ist das halt meistens. Wenn man ein Problem gelöst hat, kommen 2 andere hinzu.... Man(n) will es aber auch immer wissen, wie man so etwas lösen kann!
    Aber was ich zumindest auf den ersten Blick sehen kann ist, das eine Struktur mit "struct" angelegt werden muss, damit durch die include Anweisung der Header-Datei der Inhalt der struct in der "Form" wie auch im "Thread" genutzt werden kann. Das probiere ich gleich mal aus und das andere sehe ich mir dann mal Stück für Stück in Ruhe an.... Habe auch schon etwas neues gefunden, wo ich von der Existenz dieses noch nichts wusste: "AutoDelete"
    Das werde ich auch einsetzen.
    Danke für die wirklich gute Hilfe.
    Gruß Eric.



  • @Eric
    AutoDelete gibt´s nur für TThread. Da sämtliche Objekte, die von TObject abgeleitet sind nur auf dem Heap erzeugt werden können hat man bei Fire&Forget Threads keine Möglichkeit, das Objekt später wieder zu löschen, jedenfalls nicht ohne Aufwand. Bei

    class MyThread : public TThread
    {
       __fastcall MyThread () 
       {
          AutoDelete = true;
       }
    
       void __fastcall Execue()
       {
          ...
       }
    }
    
    void async_work()
    {
       new MyThread();
    }
    

    ensteht kein Speicherleck, weil sich der Thread nach Verlassen der Execute Methode selbst wegräumt.



  • @DocShoe sagte in while abbrechen in Objekt orientierter Programmierung:
    ...

      AutoDelete = true;
    

    Heisst das nicht eher:

    FreeOnTerminate = true;
    


  • @Burkhi
    Ääääähhhh... möglicherweise.
    Das war'n Test für Eric 😉


Log in to reply