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
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;
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;
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
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.
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:
C/C++ Code:
1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9
__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;
}
C/C++ Code:
1 2 3 4 5 6 7 8 9
__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;
}
C/C++ Code:
1 2 3 4 5 6 7 8 9
__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:
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)?
Zur Situation mit FreeOnTerminate=true kann ich sagen, daß dein Ansatz dann nicht ganz sicher ist:
C/C++ Code:
1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9
__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;
}
C/C++ Code:
1 2 3 4 5 6 7 8 9
__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;
}
C/C++ Code:
1 2 3 4 5 6 7 8 9
__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.
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.
Zuletzt bearbeitet von Unregistrierter am 15:12:50 09.02.2010, insgesamt 1-mal bearbeitet
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?!?
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;
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;
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;
Nächstes Thema anzeigen Vorheriges Thema anzeigen
Sie können Beiträge in dieses Forum schreiben. Sie können auf Beiträge in diesem Forum antworten. Sie können Ihre Beiträge in diesem Forum nicht bearbeiten. Sie können Ihre Beiträge in diesem Forum nicht löschen. Sie können an Umfragen in diesem Forum nicht mitmachen.
c++.de ist Teilnehmer des Partnerprogramms von Amazon Europe S.à.r.l. und Partner des Werbeprogramms, das zur Bereitstellung eines Mediums
für Websites konzipiert wurde, mittels dessen durch die Platzierung von Werbeanzeigen und Links zu amazon.de
Werbekostenerstattung verdient werden kann.
Die Vervielfältigung der auf den Seiten www.c-plusplus.de, www.c-plusplus.info, www.c-sar.de, www.c-plusplus.net und www.baeckmann.de
enthaltenen Informationen ohne eine schriftliche Genehmigung des Seitenbetreibers ist untersagt
(vgl. §4 Urheberrechtsgesetz). Die Nutzung und Änderung der vorgestellten Strukturen und Verfahren in
privaten und kommerziellen Softwareanwendungen ist ausdrücklich erlaubt, soweit keine Rechte Dritter verletzt werden.
Der Seitenbetreiber übernimmt keine Gewähr für die Funktion einzelner Beiträge oder Programmfragmente, insbesondere
übernimmt er keine Haftung für eventuelle aus dem Gebrauch entstehenden Folgeschäden.