4 Stimmen

Delphi Win API CreateTimerQueueTimer-Threads und thread-sichere FormatDateTime-Abstürze

Das ist eine etwas lange Frage, aber es geht los. Es gibt eine Version von FormatDateTime, die angeblich thread-sicher ist, da man

GetLocaleFormatSettings(3081, FormatSettings); 

um einen Wert zu erhalten, den Sie dann wie folgt verwenden können;

FormatDateTime('yyyy', 0, FormatSettings); 

Stellen Sie sich nun zwei Timer vor, einen mit TTimer (Intervall z.B. 1000ms) und einen weiteren Timer, der so erstellt wurde (Intervall 10ms);

CreateTimerQueueTimer
(
  FQueueTimer, 
  0, 
  TimerCallback, 
  nil, 
  10, 
  10, 
  WT_EXECUTEINTIMERTHREAD
);

Nun das Unangenehme, wenn Sie im Rückruf und auch im Timer-Ereignis den folgenden Code haben;

for i := 1 to 10000 do
begin
  FormatDateTime('yyyy', 0, FormatSettings);
end;

Beachten Sie, dass es keine Zuordnung gibt. Dies führt fast sofort zu Zugriffsverletzungen, manchmal 20 Minuten später, was auch immer, an beliebigen Stellen. Wenn Sie diesen Code nun in C++Builder schreiben, stürzt er nie ab. Die Header-Konvertierung, die wir verwenden, ist die von JEDI JwaXXXX. Selbst wenn wir in der Delphi-Version Sperren um den Code herum anbringen, verzögert das nur das Unvermeidliche. Wir haben uns die ursprünglichen C-Header-Dateien angesehen, und es sieht alles gut aus. Gibt es eine andere Art und Weise, wie C++ die Delphi-Laufzeit verwendet? Die thread-sichere Version von FormatDatTime ist anscheinend reentrant. Irgendwelche Ideen oder Gedanken von jemandem, der dies schon einmal gesehen hat.

UPDATE:

Um dies ein wenig einzugrenzen, wird FormatSettings als Konstante übergeben, so spielt es eine Rolle, wenn sie die gleiche Kopie verwenden (wie es sich herausstellt, Übergabe einer lokalen Version innerhalb des Funktionsaufrufs yeilds das gleiche Problem)? Auch die Version von FormatDateTime, die die FormatSettings nimmt nicht GetThreadLocale aufrufen, weil es bereits die Locale-Informationen in der FormatSettings-Struktur hat (ich doppelt überprüft, indem Sie durch den Code).

Ich habe keine Zuweisung erwähnt, um klarzustellen, dass auf keinen gemeinsamen Speicher zugegriffen wird, also kein Sperren erforderlich ist.

WT_EXECUTEINTIMERTHREAD wird zur Vereinfachung des Problems verwendet. Ich hatte den Eindruck, dass man es nur für sehr kurze Aufgaben verwenden sollte, weil es bedeuten kann, dass das nächste Intervall verpasst wird, wenn es etwas Langes ausführt?

Wenn Sie einen einfachen alten TThread verwenden, tritt das Problem nicht auf. Worauf ich hier hinaus will, ist wohl, dass die Verwendung eines TThread oder TTimer funktioniert, aber die Verwendung eines Threads, der außerhalb der VCL erstellt wurde, nicht. Deshalb habe ich gefragt, ob es einen Unterschied in der Art und Weise gibt, wie C++ Builder die VCL/Delphi RTL verwendet.

Nebenbei bemerkt scheitert dieser Code, wie bereits erwähnt, auch (dauert aber länger), nach einer Weile, CS := TCriticalSection.Create;

  CS.Acquire;
  for i := 1 to LoopCount do
  begin
    FormatDateTime('yyyy', 0, FormatSettings);
  end;
  CS.Release;

Und jetzt kommt der Teil, den ich wirklich nicht verstehe: Ich habe dies wie vorgeschlagen geschrieben;

function ReturnAString: string;
begin
  Result := 'Test';
  UniqueString(Result);
end;

und dann innerhalb jeder Art von Timer der Code ist;

  for i := 1 to 10000 do
  begin
    ReturnAString;
  end;

Dies führt zu den gleichen Fehlern, da der Fehler, wie gesagt, nie an der gleichen Stelle innerhalb des CPU-Fensters usw. auftritt. Manchmal ist es eine Zugriffsverletzung und manchmal könnte es eine ungültige Zeigeroperation sein. Ich bin mit Delphi 2009 btw.

UPDATE 2:

Roddy (unten) weist darauf hin, dass das Ontimer-Ereignis (und leider auch Winsock, d.h. TClientSocket) die Windows-Nachrichtenpumpe verwenden (nebenbei bemerkt wäre es schön, einige nette Winsock2-Komponenten zu haben, die IOCP und Overlapping IO verwenden), daher der Versuch, davon wegzukommen. Jedoch weiß jemand, wie zu sehen, welche Art von Thread lokale Speicherung auf die CreateQueueTimerQueue eingerichtet ist?

Danke, dass Sie sich die Zeit genommen haben, über dieses Problem nachzudenken und es zu beantworten.

5voto

Bruce Punkte 420

Ich bin mir nicht sicher, ob es zum guten Ton gehört, eine "Antwort" auf meine eigene Frage zu posten, aber es erschien mir logisch, lassen Sie es mich wissen, wenn das uncool ist.

Ich glaube, ich habe das Problem gefunden, der Thread "Lokale Speicherung" hat mich dazu gebracht, eine Reihe von Spuren zu verfolgen, und ich habe diese magische Zeile gefunden;

IsMultiThread := True;

Aus der Hilfe;

"IsMultiThread wird auf true gesetzt, um anzuzeigen, dass der Speichermanager mehrere Threads unterstützen sollte. IsMultiThread wird von BeginThread und Klassenfabriken auf true gesetzt."

Dies ist natürlich no durch die Verwendung eines einzelnen VCL-Hauptthreads mit einem TTimer gesetzt, aber es wird für Sie gesetzt, wenn Sie TThread verwenden. Wenn ich es manuell einstellen, geht das Problem weg.

In C++Builder verwende ich keinen TThread, aber er erscheint, wenn ich den folgenden Code verwende;

if (IsMultiThread) {
  ShowMessage("IsMultiThread is True!");
}

dass es irgendwo automatisch für Sie eingestellt ist.

Ich bin wirklich froh, dass ich das hier gefunden habe und hoffe vergeblich, dass es jemand anderem helfen könnte.

0 Stimmen

Wenn Sie einen Standard-Delphi-Thread erstellen, ist es nicht notwendig, diesen Wert zu setzen. Aber manchmal, wie z.B. in einer DLL, wo Sie mit mehreren Threads aufgerufen werden, müssen Sie ihn selbst setzen. Gute Stelle.

1voto

PetriW Punkte 1225

Da DateTimeToString, das FormatDateTime aufruft, GetThreadLocale verwendet, können Sie versuchen, eine lokale FormatSettings-Variable für jeden Thread zu haben, vielleicht sogar FormatSettings in einer lokalen Variablen vor der Schleife einzurichten.

Es kann auch der Parameter WT_EXECUTEINTIMERTHREAD sein, der dies verursacht. Beachten Sie, dass dieser Parameter nur für sehr kurze Aufgaben verwendet werden sollte.

Wenn das Problem weiterhin besteht, kann das Problem tatsächlich woanders liegen, was meine erste Vermutung war, als ich dies sah, aber ich habe nicht genug Informationen darüber, was der Code tut, um das wirklich zu bestimmen.

Angaben darüber, wo die Zugriffsverletzung auftritt, können hilfreich sein.

1voto

Rob Kennedy Punkte 158781

Sind Sie sicher, dass dies irgendetwas zu tun hat mit FormatDateTime ? Sie haben darauf hingewiesen, dass es dort keine Zuweisungsanweisung gibt; ist das ein wichtiger Aspekt Ihrer Frage? Was passiert, wenn Sie stattdessen eine andere Funktion aufrufen, die einen String zurückgibt? (Stellen Sie sicher, dass es sich nicht um eine konstante Zeichenkette handelt. Schreiben Sie Ihre eigene Funktion, die Folgendes aufruft UniqueString(Result) vor der Rückkehr).

Ist die FormatSettings Variable thread-spezifisch? Das ist der Sinn des zusätzlichen Parameters für FormatDateTime so dass jeder Thread seine eigene private Kopie hat, die garantiert von keinem anderen Thread geändert wird, solange die Funktion aktiv ist.

Ist die Timer-Warteschlange für diese Frage wichtig? Oder erhalten Sie die gleichen Ergebnisse, wenn Sie eine einfache alte TThread und führen Sie Ihre Schleife in der Execute Methode?

Sie haben darauf hingewiesen, dass es sich um eine lange Frage handelt, aber es scheint, dass Sie sie in einigen Punkten kürzer fassen könnten, um den Umfang des Themas einzugrenzen.

0voto

Roddy Punkte 64661

Ich frage mich, ob die RTL/VCL-Aufrufe, die Sie machen, Zugriff auf einige Thread-lokale Speicher (TLS)-Variablen erwarten, die nicht korrekt eingerichtet sind, wenn Sie Ihren Code über die Timer-Warteschlange aufrufen?

Dies ist nicht die Antwort auf Ihr Problem, aber sind Sie sich bewusst, dass TTimer OnTimer-Ereignisse nur als Teil der normalen Nachrichtenschleife im Haupt-VCL-Thread ausgeführt werden?

0voto

Darian Miller Punkte 7580

Sie haben Ihre Antwort gefunden - IsMultiThread. Dies muss jederzeit verwendet werden, um zur Verwendung der API zurückzukehren und Threads zu erstellen. Aus MSDN: CreateTimerQueueTimer erstellt einen Thread-Pool, um diese Funktionalität zu behandeln, so dass Sie einen externen Thread haben, der mit dem Haupt-VCL-Thread ohne Schutz arbeitet. (Hinweis: Ihr CS.acquire/release tut überhaupt nichts, wenn andere Teile des Codes diese Sperre nicht respektieren).

0 Stimmen

Es ist mir nicht ganz klar, warum "CS.acquire/release tut nichts", wie ich gedacht hätte, es würde schützen, dass Code FormatDateTime an beiden Stellen, aber ich vermute, dass die Haupt-VCL den Speichermanager an anderer Stelle verwendet, die ich nicht leicht kontrollieren kann und das Problem verursacht.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X