while abbrechen in Objekt orientierter Programmierung
-
Hallo nochmal...
Also ich habe mich jetzt mal über Threads grob schlau gemacht. Im Borland Builder gibt es unter "DATEI" > "NEU" eine Auswahl "Thread Objekt". Damit ist es sehr einfach einen Thread zu erzeugen. Dieser funktioniert nun auch schon so, wie es sein sollte. Habe zum Test in den Thread nur eine Endlos-while-Schleife eingeschrieben und es lässt sich nun auch schon mit einem Stop-Button wieder zurück zu den anderen Objekten kehren. Ob die Endlos-while-Schleife im Hintergrund dann noch weiter macht, oder ob diese beendet wird, weiß ich nicht. Ist aber erst einmal egal, da das Programm nur zum Testzweck geschrieben wurde.Eine weitere Frage:
Jetzt möchte ich ja die Schleife mit einer bool Variable ordentlich beenden. Die Schleife in dem Thread kennt aber den Variablennamen nicht, obwohl ich diesen für das gesamte Programm im Programmkopf deklariert habe. Der Thread kennt aber die deklarierten Variablen und auch die eingefügten Objekte nicht!
Wie kann ich dem Thread die Deklaration übergeben, damit dieser die gleichen Deklarationen nutzen kann, wie im Hauptprogramm?
-
Wie ich schon schrieb, benutze die
Terminated
-Eigenschaft:while (!Terminated) { // ... }
-
@Th69 sagte in while abbrechen in Objekt orientierter Programmierung:
Dafür gibt es die TThread.Terminate()-Funktion und die Eigenschaft Thread.Terminated, die man dann im Thread selbst abfragen kann.
Cooperative Thread Cancellation über an den Thread gebundene Standardmittel (die z.B. ein Framework bereitstellt) ist meist keine gute Idee. Weil dabei keiner genau weiss wie Funktionen aus diversen Libraries die man verwendet darauf reagieren. Nehmen wir an wir verwenden in dem Thread zwei Funktionen aus Third-Party Libs, Outer() und Inner(). Outer() ruft Inner() auf. Inner fragt das "cancel flag" ab, und denkt sich "ah, ich bin gecancelt, ich hör jetzt auf obwohl ich nocht nicht erledigt haben was ich erledigen hätte sollen" -- erfüllt also ihren Contract nicht. Outer() dagegen weiss davon nix und verlässt sich darauf dass Inner() ihren Contract erfüllt hat.
Natürlich könnte man das "fixen" indem man alle Contracts auch bezüglich Cancellation penibel genau ausführt. Die Erfahrung zeigt aber dass es kaum jemanden schert genau definierte Contracts zu formulieren -- oder auch nur die Contracts von Third Party Libs die man verwendet genau zu lesen.
=> Besser eigene Cancel-Flags machen.
-
@hustbaer Wenn es genügt, daß TThread::Execute() das Terminate-Flag auswertet, kann man das schon so machen. Thirdparty-Libs werden i.d.R. von TThread eher nichts kennen, außer sie sind speziell für die VCL implementiert.
-
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.
- du darfst aus einem nebenläufigen Thread keine GUI Elemente aktualisieren. Das kann funktionieren, muss aber nicht.
- du musst gemeinsam benutzte Daten die Zugriffe gegeneinander verriegeln (nur wenn min. ein Schreibzugriff dabei ist)
Zu 1)
Dazu benutzt du dieSynchronize()
Methode vonTThread
.Zu 2)
Der Zugriff auf die Daten muss per CriticalSection oder Mutex geschützt werden. Vor dem Zugriff rufst duEnter
auf, wenn du damit fertig bistLeave
.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ürTThread
. Da sämtliche Objekte, die vonTObject
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. Beiclass 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