Wie erstellt man einen neuen Thread?
-
audacia schrieb:
Also reiß dich zusammen und arbeite das Tutorial durch, bevor du fortfährst.
Ach was, wann hat man sonst die Möglichkeit, so schöne Fehlermeldungen, wie 'Leinwand erlaubt kein zeichnen' zu erhalten.
Ich hab mit den VCL-Threads aber auch so meine Probleme. Ich hab keine Möglichkeit gefunden, laufende Threads, mit VCL-Mitteln, sinnvoll zu beenden, wenn der User die Anwendung beendet. Da muß man tatsächlich auf Events und WaitForXXX zurückgreifen. Oder ich hab mich wieder zu blöd angestellt...
Gruß KK
-
Ich habe das Thema wohl auf die leichte Schulter genommen. Aber nur, weil
mein Chef alles am besten gestern fertig haben will.
Danke für alle Kommentare. Ich werde gleich das Tutorial durchlesen.
-
@Audacia
Ich habe nur die 4 wichtigsten Funktionen zur Win32 Threadverwaltung genannt, vielleicht bin ich fälschlicherweise davon ausgegangen, dass der OP sich mit Thread Design und Verhinderung von race conditions auskennt (die man im übrigen auch mit TThread haben kann). Ich stimme dir insofern zu, dass man die Techniken zur Threadsynchronisation beherrschen muss, um mit den Win32 API Funktionen ein robustes Thread Design zu erstellen.Das Verhalten eines von TThread abgeleiteten Threads kann mitunter schon seltsam sein, insbesondere wenn ein Thread gestoppt werden soll, der noch nie gelaufen ist. Im Destruktor ~TThread() wird allerhand Synchroniasationszeugs getrieben, wobei es bei uns schon bis zu 30 Sekunden gedauert hat, bis die CPU aus dem Destruktoraufruf zurückgekommen ist. Leider konnten wir die genauen Randbedingungen nie vollständig ermitteln, aber ein Wechsel von TThread auf Threads durch die Win32 API hat das Problem instantan gelöst.
Wie du schon erwähntest steht man dann aber vor dem Problem, dass man aus den Win32 Threads keine GUI Aktualisierung anstossen kann (jedenfalls nicht durch direkte Methodenaufrufe irgendwelcher VCL Objekte), da die VCL kein Multithreading beherrscht und von sich aus auch keine Hilfsmitteln anbietet, um sich mit nicht VCL-Threads zu synchronisieren. Ich denke aber, dass man eine Synchronisation mit benutzerdefinierten Nachrichten und PostMessage hinbekommt.
-
sonic_1233 schrieb:
Aber nur, weil mein Chef alles am besten gestern fertig haben will.
Das ist eigentlich immer der Fall, und nie eine gute Ausrede für laxes Vorgehen
DocShoe schrieb:
Ich habe nur die 4 wichtigsten Funktionen zur Win32 Threadverwaltung genannt
Nein, leider nicht. Du hast vier Funktionen genannt, die man allesamt, mit der Ausnahme von ResumeThread speziell für CREATE_SUSPENDED, tunlichst meiden sollte
Die wichtigsten Funktionen zur Win32-Threadverwaltung heißen _beginthread[ex](), _endthread[ex](), WaitForSingleObject und Create/SetEvent/ResetEvent.
DocShoe schrieb:
Im Destruktor ~TThread() wird allerhand Synchroniasationszeugs getrieben, wobei es bei uns schon bis zu 30 Sekunden gedauert hat, bis die CPU aus dem Destruktoraufruf zurückgekommen ist. Leider konnten wir die genauen Randbedingungen nie vollständig ermitteln
TThread.Destroy sieht so aus:
destructor TThread.Destroy; begin if (FThreadID <> 0) and not FFinished and not FExternalThread then begin Terminate; if FCreateSuspended then Resume; WaitFor; end; RemoveQueuedEvents(Self); {$IFDEF MSWINDOWS} if (FHandle <> 0) and not FExternalThread then CloseHandle(FHandle); {$ENDIF} {$IFDEF LINUX} // This final check is to ensure that even if the thread was never waited on // its resources will be freed. if (FThreadID <> 0) and not FExternalThread then pthread_detach(FThreadID); sem_destroy(FCreateSuspendedSem); {$ENDIF} inherited Destroy; FFatalException.Free; end;
Die einzige Stelle, an der realistisch gesehen 30s-Verzögerungen entstehen können ist "WaitFor;" - und das hängt natürlich davon ab, was in deiner Execute()-Methode passiert. Wenn du da nicht gelegentlich überprüfst, ob das Terminate-Flag gesetzt ist, kanns natürlich lange dauern.
DocShoe schrieb:
Wie du schon erwähntest steht man dann aber vor dem Problem, dass man aus den Win32 Threads keine GUI Aktualisierung anstossen kann (jedenfalls nicht durch direkte Methodenaufrufe irgendwelcher VCL Objekte), da die VCL kein Multithreading beherrscht und von sich aus auch keine Hilfsmitteln anbietet, um sich mit nicht VCL-Threads zu synchronisieren. Ich denke aber, dass man eine Synchronisation mit benutzerdefinierten Nachrichten und PostMessage hinbekommt.
Klar, irgendwie möglich ist es schon - warum einfach, wenns auch umständlich geht
-
audacia schrieb:
Die einzige Stelle, an der realistisch gesehen 30s-Verzögerungen entstehen können ist "WaitFor;" - und das hängt natürlich davon ab, was in deiner Execute()-Methode passiert. Wenn du da nicht gelegentlich überprüfst, ob das Terminate-Flag gesetzt ist, kanns natürlich lange dauern.
Ich hatte das gleiche Problem: Klick mich
Wenn Du Zeit und Muße hast da mal reinzuschauen... Vielleicht kannst Du mir ja sagen, was ich da falsch gemacht habe.
-
Joe_M. schrieb:
Wenn Du Zeit und Muße hast da mal reinzuschauen... Vielleicht kannst Du mir ja sagen, was ich da falsch gemacht habe.
Zur Situation mit FreeOnTerminate=true kann ich sagen, daß dein Ansatz dann nicht ganz sicher ist:
__fastcall TForm1::~TForm1(void) { FMyThread->Terminate(); // -> der Thread kann ab sofort jederzeit terminiert // werden FMyThread->WaitFor(); // ... und was, wenn das hier schon passiert ist? Dann // hat sich das Objekt bereits selbst gelöscht - fatal. delete FMyThread; // Noch viel schlimmer - wir zerstören es zweimal. FMyThread = NULL; }
FreeOnTerminate und Terminate()/WaitFor() schließen einander praktisch aus. Mehr noch: wenn ein Thread FreeOnTerminate=true verwendet, darfst du, sobald er läuft, eigentlich nicht mehr von außen auf das Thread-Objekt zugreifen - du weißt nie, ob es überhaupt noch existiert. In diesem Fall ist es notwendig, sich auf andere Kommunikationsmechanismen zu beschränken.
Selbst wenn du weißt, was der Thread macht und ausschließen kannst, daß er terminiert, bevor du Terminate() von außen aufgerufen hast, so darfst du nach dem Aufruf von Terminate() das Objekt nicht mehr benutzen, was auch ein WaitFor() ausschließt (derartige Versuche werden auch üblicherweise mit einer Exception quittiert). Infolgedessen kann dein Hauptthread nicht warten, bis der Hintergrundthread ordentlich aufgeräumt hat - und da die Beendigung des Hauptthreads auch den Prozeß beendet, wird der Hintergrundthread infolgedessen von Windows terminiert.
Außerdem das hier, was zwar nichts mit dem Threading-Problem zu tun hat, aber mir doch bemerkenswert erscheint:
__fastcall TMonitorDirThread::~TMonitorDirThread() { ... TThread::inherited (); }
Deaktiviere mal Laufzeitpackages, nimm dir den Debugger, setze da einen Breakpoint und schau, was da passiert.
Und um dir gleich mal den Spaß zu verderben: TThread::inherited ist ein Typedef auf TObject; du rufst also den TObject-Konstruktor auf, der ein neues, nicht initialisiertes TThread-Objekt erstellt. Ich weiß nicht, was genau du da vorhattest - vielleicht dachtest du daran, daß man beim Überschreiben virtueller Funktionen den Aufruf gelegentlich an die Basisklasse weiterleiten muß? Das ist zwar richtig, trifft jedoch auf Konstruktoren und Destruktoren in C++ nicht zu; dort passiert das automatisch. (In Delphi hingegen wäre ein "inherited Destroy;" hier durchaus richtig.)
Eine 10-15s anhaltende Verzögerung konnte ich nicht feststellen. Ich habe allerdings nur dein erstes Beispiel ausführlich getestet. Ist das mit dem zweiten besser reproduzierbar, und kannst du noch etwas ins Detail gehen hinsichtlich der notwendigen Randbedingungen (u.a. C++Builder-Version)?
-
Wie kann man eigentlich auf die Eigenschaft "Terminated" zugreifen?
Innerhalb von Execute() geht das ohne Problemeif(!Terminated) { // tu irgendwas }
-
audacia schrieb:
Zur Situation mit FreeOnTerminate=true kann ich sagen, daß dein Ansatz dann nicht ganz sicher ist:
__fastcall TForm1::~TForm1(void) { FMyThread->Terminate(); // -> der Thread kann ab sofort jederzeit terminiert // werden FMyThread->WaitFor(); // ... und was, wenn das hier schon passiert ist? Dann // hat sich das Objekt bereits selbst gelöscht - fatal. delete FMyThread; // Noch viel schlimmer - wir zerstören es zweimal. FMyThread = NULL; }
FreeOnTerminate und Terminate()/WaitFor() schließen einander praktisch aus. Mehr noch: wenn ein Thread FreeOnTerminate=true verwendet, darfst du, sobald er läuft, eigentlich nicht mehr von außen auf das Thread-Objekt zugreifen - du weißt nie, ob es überhaupt noch existiert. In diesem Fall ist es notwendig, sich auf andere Kommunikationsmechanismen zu beschränken.
Du hättest beim Beispiel aus dem ersten Post bleiben sollen.
Das ist die ursprüngliche Fassung des Threads, der das Verhalben mit diesem Minimalbeispiel zeigt. Und dort ist FreeOnTerminate = false. Bei dem späteren Beispiel hatte ich dann in meienr Verzweiflung alles mögliche getestet, unter anderem auch, was ich so an Hinweisen im Netz gefunden habe... Bis hin zum Inherited.
audacia schrieb:
Eine 10-15s anhaltende Verzögerung konnte ich nicht feststellen. Ich habe allerdings nur dein erstes Beispiel ausführlich getestet. Ist das mit dem zweiten besser reproduzierbar, und kannst du noch etwas ins Detail gehen hinsichtlich der notwendigen Randbedingungen (u.a. C++Builder-Version)?
Das ist im BCB 6 und mit dem Code aus dem ersten Post ist das Problem reproduzierbar. Ich habe das Problem ja auch schlußendlich lösen können, auf Basis des Codes meinem zweiten Post. Allerdings nicht mir VCL-Mitteln, sondern eben mit Events. Keine Wartezeit am Ende, Destruktor der Threads wird aufgerufen (was nach Terminate() auch nicht der Fall ist). Und wie gesagt, die Probleme tauchen nicht im laufenden Betrieb auf, sondern nur, wenn die Anwendung beendet wird und man noch aufräumen möchte, sprich die Threads beenden und freigeben.
-
Joe_M. schrieb:
Das ist im BCB 6 und mit dem Code aus dem ersten Post ist das Problem reproduzierbar.
Dann mache ich irgendetwas falsch.
Kannst du evtl. mal ein vollständiges Projekt hochladen?
-
Na klar. Hier ist ein Minimalprojekt: LINK ENTFERNT
Sogar ohne, dass da sonst irgendetwas passiert, dauert der Destruktoraufruf von Form1 ca. 7 Sekunden.
Das alte Projekt habe ich in dieser Form nicht mehr, sondern nur noch die auf Events umgestellte Fassung. Aber die funktioniert ja klaglos und das Beenden und Löschen der Threads bewegt sich im Millisekundenbereich...
Edit: Link entfernt.
-
Verrückt - ich habe das jetzt mehrere Male durchlaufen lassen, und ich kann es nach wie vor nicht reproduzieren.
Die einzige wahrnehmbare Verzögerung sind die 200ms des Sleep() in den Hintergrundthreads; das kann man durch Vergrößerung der Verzögerung entsprechend skalieren. Aber das sind maximal 0.8s, in der Praxis meist nur 0.4.
Was passiert denn während dieser 7s-Verzögerungen, bzw. wo genau treten sie auf (-> ohne Packages und mit Debug-Bibliotheken kompilieren)? Treten sie überhaupt auf, wenn du schrittweise durch den Code gehst?
-
Die Verzögerung tritt beim WaitFor() auf. Wenn ich das rausnehme, tritt die Verzögerung beim delete auf. Und ja, die Verzögerung habe ich auch beim einzelnen durchsteppen.
Einstellung sind die Standardeinstellungen für ein neues Projekt im BCB 6 (respektive genau so, wie in dem hochgeladenen Projekt).
Wenn ich auf 'Endgültig', ohne Laufzeit-Packages und ohne dynamische RTL kompiliere, verdoppelt sich die Wartezeit fast (auf 12 bis 16 Sekunden). Spontan hätte ich gesagt, dass sich die Zeit eher etwas reduziert?!?Mit welcher Version testest Du?
-
C++Builder 6; ich hab es aber auch mit C++Builder 2010 getestet, und es verhält sich da nicht anders. (Windows XP SP3.)
Wenn du mit Debug-Bibliotheken kompilierst und während der 7s-Verzögerung das Programm unterbrichst, wird der Call-Stack vermutlich auf eine Zeile von TThread.WaitFor verweisen. Welche ist das genau?
function TThread.WaitFor: LongWord; {$IFDEF MSWINDOWS} var H: THandle; WaitResult: Cardinal; Msg: TMsg; begin H := FHandle; if GetCurrentThreadID = MainThreadID then begin WaitResult := 0; repeat { This prevents a potential deadlock if the background thread does a SendMessage to the foreground thread } if WaitResult = WAIT_OBJECT_0 + 1 then PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE); Sleep(0); CheckSynchronize; // <-- das hier? WaitResult := MsgWaitForMultipleObjects(1, H, False, 0, QS_SENDMESSAGE); // <-- oder das hier? Win32Check(WaitResult <> WAIT_FAILED); until WaitResult = WAIT_OBJECT_0; end else WaitForSingleObject(H, INFINITE); // <-- oder gar das hier? (wäre seltsam, da ich GetCurrentThreadID = MainThreadID erwarten würde) CheckThreadError(GetExitCodeThread(H, Result)); end;
-
In diesem Forum gibt es anscheinend ein Tutorial für Threads:
http://bcb-tutorial.c-plusplus.net/Thread/artikel5.html
Ich glaube da ist ein Fehler drin.
Dort wird im Thread-Konstruktor FreeOnTerminate auf true gesetzt. Das bedeutet doch, dass der Thread nach der Ausführung von "Execute" sich selbstständig terminiert. Und in der Cleanup-Funktion wird "Terminate()" aufgerufen. Das ist doch überflüssig, oder?
-
@sonic:
Ich habe mir das Tutorial jetzt nicht angesehen, aber der Aufruf von Terminate() beendet einen Thread. Wenn FreeOnTerminate=true ist, muss man weiter nichts machen. Wenn es false ist, muss man den Thread auch noch löschen.
Der Aufruf von Terminate() ist nicht grundsätzlich verboten, auch wenn der Thread mit FreeOnTerminate=true ist. Es gibt Threads die laufen in einer kontunierlichen Schleife, diese muss man mit Terminate() beenden. Wenn ein Thread allerdings eine eigene Abbruchbedingung hat, sollte man Terminate() nicht verwenden.@audacia
Der bleibt in der Zeile 19, also dem WaitResult := ... stehen. Zumindest ist das die aufrufende Funktion. Viel häufiger wird mir allerdings der Aufruf von Sleep als aufrufende Funktion angezeigt (in 9 von 10 Fällen).
Das verstehe ich dann jetzt überhaupt nicht. Ich mein, es ist nur ein 200 ms Sleep, wie kann das mehrere Sekunden beanspruchen?!? Könnte das an meiner Hardware liegen? Der Rechner ist zugegebenermaßen schon ziemlich alt.
-
Joe_M. schrieb:
@audacia
Der bleibt in der Zeile 19, also dem WaitResult := ... stehen. Zumindest ist das die aufrufende Funktion. Viel häufiger wird mir allerdings der Aufruf von Sleep als aufrufende Funktion angezeigt (in 9 von 10 Fällen).
Das verstehe ich dann jetzt überhaupt nicht. Ich mein, es ist nur ein 200 ms Sleep, wie kann das mehrere Sekunden beanspruchen?!? Könnte das an meiner Hardware liegen? Der Rechner ist zugegebenermaßen schon ziemlich alt.Wir hatten bei einem Projekt genau das gleiche Verhalten, nur dass es nicht 7 Sekunden, sondern 30+ waren. Das Szenario war das gleiche, im Destruktor des TThreads blieb die CPU auf WaitFor stehen. Das Verhalten war nicht immer 100%ig (eigentlich war das korrekte Verhalten nicht reproduzierbar, in 4 von 5 Fällen hing der Thread im Destruktor) reproduzierbar, es trat sowohl auf zwei Entwicklungsrechnern als auch auf einem Produktionssystem auf.
-
Hallo
Das Problem mit dem blockieren im WaitFor des Threads liegt darin, dass WaitFor intern einen Sleep(0) verwendet. Dieser gibt den Prozessor nur an Threads mit gleicher Priorität ab.
Wenn auf einen Thread mit tieferer Priorität gewartet wird, erhält dieser keine CPU-Zeit (ausser auf Mehr-Prozessor-Systemen).
Zur Abhilfe sollte die Priorität des Threads, auf den gewartet wird vor dem WaitFor (oder vor Terminate) auf mindestens die Priorität des aktuellen Threads angehoben werden.Gruss Chis
-
Ich bin sprachlos. Mit einem Priority = tpHigher vor der Terminate()-Aufruf, ist das Problem tatsächlich weg. Was hab ich damals nicht alles probiert, aber darauf bin ich nicht gekommen...
Aber im Beispiel zu Priority wird die Priorität gesetzt, bevor der Thread mit Resume fortgesetzt ist. Ist das nur Zufall, oder soll das ein Hinweis darauf sein, dass man die Priorität zur Laufzeit nicht ändern sollte?
-
Ich habe schon öfters Thread-Prioritäten zur Laufzeit geändert. Konnte bisher nie irgendein Nachteil feststellen. In der Doku habe ich auch keine Hinweise gefunden, die dies als schlecht einstufen.
Gruss Chris
-
Chrisi_K schrieb:
Das Problem mit dem blockieren im WaitFor des Threads liegt darin, dass WaitFor intern einen Sleep(0) verwendet. Dieser gibt den Prozessor nur an Threads mit gleicher Priorität ab.
Wenn auf einen Thread mit tieferer Priorität gewartet wird, erhält dieser keine CPU-Zeit (ausser auf Mehr-Prozessor-Systemen).Tatsächlich - ist ja auch so in der Dokumentation zu Sleep() nachlesbar. *facepalm*
Zu dem Problem gibt es auch schon einen QC-Report: #3260. Allerdings sollte das Problem mittlerweile nicht mehr auftreten, denn in C++Builder 2006 und höher verwendet TThread.WaitFor kein Sleep(0) mehr:
function TThread.WaitFor: LongWord; {$IFDEF MSWINDOWS} var H: array[0..1] of THandle; WaitResult: Cardinal; Msg: TMsg; begin H[0] := FHandle; if GetCurrentThreadID = MainThreadID then begin WaitResult := 0; H[1] := SyncEvent; repeat { This prevents a potential deadlock if the background thread does a SendMessage to the foreground thread } if WaitResult = WAIT_OBJECT_0 + 2 then PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE); WaitResult := MsgWaitForMultipleObjects(2, H, False, 1000, QS_SENDMESSAGE); CheckThreadError(WaitResult <> WAIT_FAILED); if WaitResult = WAIT_OBJECT_0 + 1 then CheckSynchronize; until WaitResult = WAIT_OBJECT_0; end else WaitForSingleObject(H[0], INFINITE); CheckThreadError(GetExitCodeThread(H[0], Result)); end; {$ENDIF}
Das erklärt zwar nicht, weshalb ich das Problem mit C++Builder 6 nicht reproduzieren kann (SetProcessAffinityMask() habe ich natürlich getestet), aber in neueren Versionen sollte es nicht mehr auftreten.