Thread sofort(!) und "sauber" beenden.
-
Hallo,
ich habe folgendes Problem:
ich starte vom Hauptfenster einen Thread mit der VCL-Klasse TThread. In diesem wird neben Speicherreservierungen auch die serielle Schnittstelle geöffnet und geschlossen. Nach dem ordnungsgemässen Ablauf (Dauer ca. 2 Minuten) beendet sich der Thread, schliesst die Schnittstelle und gibt die Ressourcen wieder frei.
Jetzt soll dieser Thread vom Benutzer durch den Click auf einen Button vorzeitig beendet werden können. Die Verzögerung soll im für den Benutzer nicht merkbaren Bereich liegen, <1s.
Ich kenne prinzipiell zwei Möglichkeiten einen Thread zu beenden:
1. TerminateThread ... Thread wird sofort beendet, aber die Schnittstelle wird so nicht geschlossen, der reservierte Speicher nicht freigegeben. Also unbrauchbar.2. Threadname->Terminate() ... Dadurch wird die Eigenschaft "terminated" auf true gesetzt, der Thread aber nicht sofort beendet. Ich müsste terminated in regelmässigen Abständen im Thread abfragen. Würde funktionieren, ist aber unschön, unübersichtlich und aufwendig.
Kennt einer eine Möglichkeit, wie ich vom Hauptfenster aus den Thread sofort und "sauber" beenden kann?
Meine Gedanken gingen schon in die Richtung try, catch, throw. Also vom Hauptfester aus gezielt eine Exception werfen. Dies scheint aber nicht Threadübergreifend zu funktionieren.Grüße,
Jochen
-
Threadname->Terminate() ... Dadurch wird die Eigenschaft "terminated" auf true gesetzt, der Thread aber nicht sofort beendet. Ich müsste terminated in regelmässigen Abständen im Thread abfragen. Würde funktionieren, ist aber unschön, unübersichtlich und aufwendig.
Das ist aber Sinn der Sache, dies exakt in OnExecute zu tun. In einer while Schleife terminated abzufragen.
Des Weiteren gibt es noch das Ereignis OnTerminated. Damit könntest du nach 1. vorgehen. Das Ereignis wird dann ausgeführt und könnte aufräumen.
-
Christian211 schrieb:
Das ist aber Sinn der Sache, dies exakt in OnExecute zu tun. In einer while Schleife terminated abzufragen.
Schon klar dass das der Sinn der Sache ist. Leider nützt mir halt dieser Sinn relativ wenig. Zur Verdeutlichung: Aus Execute() rufe ich nacheinander mehrere Funktionen auf, die unterschiedlich lange dauern, teilweise bis zu 30s. Ich müsste jetzt innerhalb dieser Funktionen immer wieder terminated abfragen um die Funktion dann vorzeitig zu verlassen, die restlichen Funktionen überspringen um dann an das Ende von Execute() zu gelangen wo mit delete alles aufgeräumt wird. Mein Execute wird nur einmal abgearbeitet, es gibt da keine Schleife!
Des Weiteren gibt es noch das Ereignis OnTerminated. Damit könntest du nach 1. vorgehen. Das Ereignis wird dann ausgeführt und könnte aufräumen.
Siehe oben: wenn ich grad in einer Funktion stecke die 30s dauert, erst 2s davon vergangen sind, dann dauert es noch 28s bis der Code in OnTerminated überhaupt ausgeführt wird. Hier noch das wichtigste aus meinem Execute:
void __fastcall TPruefen::Execute() { SetName(); char buffer[BUFFSIZE]; int RetValue; Abbruch = false; MainWin->Abbruch->Enabled = true; MainWin->MenuStart->Enabled = false; MainWin->PruefErgebnis->Caption = "Prüfung läuft"; MainWin->PruefErgebnis->Color = clBtnFace; //serielle Schnittstelle öffnen C1 = new USS(MainWin->sCom, 9600, 8, EVEN, 1, USS2); if (C1->ComErrorCode != 0) { ErrMsg = "Fehler beim Öffnen von COM"; ErrMsg += IntToStr(MainWin->sCom); ErrMsg += ": "; ErrMsg += C1->ComSystemErrorStr; Typ = mtError; Buttons = TMsgDlgButtons() << mbOK; Synchronize (MyMessage); Abbruch = true; } //Prüfablauf.... if (!Abbruch && !this->Terminated) RetValue = SetParameter(); if (RetValue != 0) Abbruch = true; if (!Abbruch && !this->Terminated) RetValue = CheckOptionen(); //Diese und die nachfolgenden Funktionen dauern jeweils bis zu 30s if (RetValue != 0) Abbruch = true; if (!Abbruch && !this->Terminated) RetValue = DigitalInOut(); if (RetValue != 0) Abbruch = true; if (!Abbruch && !this->Terminated) RetValue = AnalogInOut(); if (RetValue != 0) Abbruch = true; if (!Abbruch && !this->Terminated && Option1 == CO ) RetValue = TestCanOpen(); if (RetValue != 0) Abbruch = true; if (!Abbruch && !this->Terminated && Option1 == PB ) RetValue = TestProfibus(); if (RetValue != 0) Abbruch = true; if (!Abbruch && !this->Terminated && Option1 == CB ) RetValue = TestCanbus(); if (RetValue != 0) Abbruch = true; //Prüfergebnis ausgeben // // genauere Auswertung von RetValue muss noch erledigt werden. // if (Abbruch) { MainWin->PruefErgebnis->Caption = "FEHLER"; MainWin->PruefErgebnis->Color = clRed; } else { MainWin->PruefErgebnis->Caption = "OK"; MainWin->PruefErgebnis->Color = clLime; } if (this->Terminated) { MainWin->PruefErgebnis->Caption = "ABBRUCH"; MainWin->PruefErgebnis->Color = clRed; } delete C1; MainWin->MenuStart->Enabled = true; MainWin->Abbruch->Enabled = false; }
-
Christian hat dir die Antwort auf die Frage ja schon gegeben: Außer der regelmäßigen Prüfung von Terminated gibt es keine saubere Lösung.
Deshalb von mir nur noch der Hinweis auf Synchronize(). Du mußt dieses einsetzen, um VCL-Komponenten in einer Form, aus einem Thread heraus, zu aktualisieren. Sonst wird es früher oder später Access-Violations oder sonstiges unvorgesehenes Verhalten geben.
-
Abgesehen davon, dass ich vermutlich die Architektur dieses Teils der Anwendung anders gwählt hätte, würde ich hier als instant-Lösung ein etwas anderes Konzept vorschlagen. Allerdings müsste ich zuerst wissen: Was genau muss mit der Reaktionszeit <1s passiert sein? Bzw. wieso definierst du die Thread-Beende-Zeit <1s?
Eine weitere Frage: ich sehe du prüfst da diverse Bussysteme... sind die Kommunikationslayer da von dir oder fertige Libraries?
-
junix schrieb:
Abgesehen davon, dass ich vermutlich die Architektur dieses Teils der Anwendung anders gwählt hätte, würde ich hier als instant-Lösung ein etwas anderes Konzept vorschlagen. Allerdings müsste ich zuerst wissen: Was genau muss mit der Reaktionszeit <1s passiert sein? Bzw. wieso definierst du die Thread-Beende-Zeit <1s?
Stell Dir das mal so vor: der Benutzer steht vor dem PC, klickt auf "Start", bemerkt dann dass er z.B. das Gerät nicht eingeschaltet hat oder vergessen hat ein Kabel einzustecken oder was auch immer. Also klickt er auf "Abbruch" um die Prüfung(=Thread) sofort zu beenden, steckt das Kabel und soll natürlich sofort wieder auf "Start" klicken können und nicht erst 10 oder 20s warten müssen. Deshalb soll eben innerhalb dieser Zeit der Thread komplett beendet und sauber "entsorgt" sein --> Schnittstelle schliessen, Speicher freigeben, Thread beenden.
Nochmal: es ist mir bewusst, dass die normale Vorgehensweise Threadname->Terminate() und terminated ist, aber das halte ich da für unschön.Eine weitere Frage: ich sehe du prüfst da diverse Bussysteme... sind die Kommunikationslayer da von dir oder fertige Libraries?
Die sind von mir.
@Joe_M: danke für den Hinweis wegen Synchronize. Ich habe es oben für die Fehlermeldung mir der Schnittstelle ja noch gemacht, für den Rest schlichtweg nicht beachtet

-
JoKo schrieb:
Meine Gedanken gingen schon in die Richtung try, catch, throw. Also vom Hauptfester aus gezielt eine Exception werfen. Dies scheint aber nicht Threadübergreifend zu funktionieren.
trotzdem sind exceptions eine gute idee. ich stelle mir das so vor, daß in der tat der thread vergiftet werden muß. also eine threadlokale variable wird gesetzt. nehmen wir ruhig gleich this->Terminated. und dauernd wird geprüft, ob die variable gesetzt ist und wenn sie gesetzt ist, wirfst du ne exception.
die exception mußt du in der äußersten thread-funktion aber fangen, wenn die versucht, den thread zu verlassen, gibt's aua.
vielleicht wäre zusätzlich zur variablen ein Event keine sehr schlechte idee, damit du auch immer WaitForSingleObject statt Sleep machen kannst, und gegebenefalls ein wartebefehl sofort abbricht. sozusagen ein MySleep(int millisekunden). das würde dann die exception werfen, wenn der thread abgebrochen werden soll. vielleicht noch ne void MyCheckForTerminate(), die die variable checkt und gegebenenfalls die exception wirft. die müßte regelmäßig aufgerufen werden. bei schnellen schleifen kann man sich sehr rechenzeitschonend helfen mitchar count=0; for(lange schleife){ tuWasSchnelles(); if(!++count) MyCheckForTerminate(); }und ansonsten halt immer prüfen, nachdem was aufwendiges gemacht wurde.
wenn du auf die serielle schnittstelle wartest, könnte vielleicht auch das Event helfen, zusammen mit WaitForMultipleObjects. nö, feiner sieht da aber mal CancelSynchronousIo() aus. mußt dir halt schnell Vista installieren, damit das geht. oder schreib irgendwas und plane, die neue funktion einzubauen, sobald der kunde Vista benutzt.
-
JoKo schrieb:
Eine weitere Frage: ich sehe du prüfst da diverse Bussysteme... sind die Kommunikationslayer da von dir oder fertige Libraries?
Die sind von mir.
Bestens. Ich hätte da nen Vorschlag einer kleinen architektonischen Änderungen unterhalb des Threads an dich.
Vielleicht ein paar Gedanken vorab:
Abstrahiere jede Kommunikationsmöglichkeit in einer Klasse wie z.B. "TBus"
Erben der abstrakten Klasse TBus sind dann Com-Port-Can-Bus etc.TBus stellt alle generischen Sende- und Empfangsroutinen bereit.
Ebenso ist Teil des Interfaces TBus eine Routine "BrichSofortAllesAb".
Wenn sich dein Trhead nun auf die Suche nach einer Gegenstelle begibt, instanziert er nacheinander die unterschiedlichen TBus-Erben und macht, was auch immer gemacht werden muss.
Jetzt kannst du von deinem TThread-Erben (TPruefen) die Methode Terminate() überschreiben, in der du - vor dem Aufruf der Mutter-Funktion - von TBus die BrichAllesAb-Methode aufrufst.
Sähe dann wohl etwa so aus:
class TBus { public: TBus(); virtual ~TBus(); virtual void oeffne(void) = 0; virtual void sende(void) = 0; virtual void empfange(void) = 0; virtual void abort(void) = 0; virtual void schliesse (void) = 0; }; class TComPort : public TBus { TComPort(); virtual ~TComPort(); virtual void oeffne(void); virtual void sende (void); virtual void empfange (void); virtual void abort (void); virtual void schliesse (void); }; void TComPort::oeffne(void) { //--COM-Port-Öffnen und konfigurieren } void TComPort:: sende (void) { //--Daten senden } void TComPort:: empfange (void) { //--Daten empfangen } void TComPort:: abort (void) { //--Alle Operationen sofort abbrechen. } void TComPort:: schliesse (void) { //--COM-Port-schliessen } class TCAN : public TBus { TCAN (); virtual ~ TCAN (); virtual void oeffne(void); virtual void sende (void); virtual void empfange (void); virtual void abort (void); virtual void schliesse (void); }; void TCAN::oeffne(void) { //--CAN-Port-Öffnen und konfigurieren } void TCAN:: sende (void) { //--Daten senden } void TCAN:: empfange (void) { //--Daten empfangen } void TCAN:: abort (void) { //--Alle Operationen sofort abbrechen. } void TCAN:: schliesse (void) { //--CAN-Port-schliessen } class TPruefen : public TThread { … private: TBus *CommObject_TBus; … } void __fastcall TPruefen::Execute(void) { /* init */ for (unsigned int i = 0;i < ANZAHL_BUS_ARTEN;i++) { switch (i) { case 0: { CommObject_TBus = new TComPort(); break; } case 1: { CommObject_TBus = new TComPort(); break; } default: { /* Sollte ein Bustyp unbekannt sein, wird er übersprungen, und die Schleife verlassen. */ i = ANZAHL_BUS_ARTEN; CommObject_TBus = NULL; break; } } if (CommObject_TBus == NULL) continue; /* Wurde dein Objekt erfolgreich angelegt, wird die Prüfung vorgenommen */ CommObject_TBus->sende(); CommObject_TBus->empfange(); delete CommObject_TBus; } /* Cleanup */ } void __fastcall TPruefen::Terminate(void) { /* Selbstverständlich sind hier noch zugriffsroutinen und Synchronisationsmechanismen unerlässlich (CriticalSections, etc…) */ if (CommObject_TBus != NULL) CommObject_TBus->abort(); /* Terminate-Methode der Mutterklasse aufrufen. */ TThread::Terminate(); }Keine Ahnung ob das verständlich ist. Aber sicher verständlicher als ein paar Worte...
-
junix schrieb:
Keine Ahnung ob das verständlich ist. Aber sicher verständlicher als ein paar Worte...
Das ist verständlich und sicher eine Überlegung wert. Ich danke Dir für die Mühe die Du Dir gemacht hast

Jochen