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.