这是一个有点长的问题,但我们开始吧。有一个 FormatDateTime 版本,据说是线程安全的,您可以使用
GetLocaleFormatSettings(3081, FormatSettings);
得到一个值,然后你可以像这样使用它;
FormatDateTime('yyyy', 0, FormatSettings);
现在想象两个定时器,一个使用 TTimer(间隔说 1000 毫秒),然后另一个定时器是这样创建的(10 毫秒间隔);
CreateTimerQueueTimer
(
FQueueTimer,
0,
TimerCallback,
nil,
10,
10,
WT_EXECUTEINTIMERTHREAD
);
现在最重要的是,如果在回调和计时器事件中,您有以下代码;
for i := 1 to 10000 do
begin
FormatDateTime('yyyy', 0, FormatSettings);
end;
注意没有赋值。这几乎会立即产生访问冲突,有时会在 20 分钟后,无论如何,在随机的地方。现在,如果您在 C++Builder 中编写该代码,它永远不会崩溃。我们使用的标头转换是 JEDI JwaXXXX 的。即使我们在 Delphi 版本中围绕代码加锁,也只会延迟不可避免的情况。我们查看了原始的 C 头文件,一切看起来都不错,C++ 使用 Delphi 运行时有什么不同的方式吗?FormatDatTime 的线程安全版本看起来是可重入的。任何可能以前看过此内容的人的任何想法或想法。
更新:
为了缩小范围,FormatSettings 作为 const 传入,所以它们是否使用相同的副本是否重要(因为在函数调用中传递本地版本会产生同样的问题)?此外,采用 FormatSettings 的 FormatDateTime 版本不会调用 GetThreadLocale,因为它已经在 FormatSettings 结构中具有区域设置信息(我通过单步执行代码进行了仔细检查)。
我提到没有分配以明确表示没有访问共享存储,因此不需要锁定。
WT_EXECUTEINTIMERTHREAD 用于简化问题。我的印象是你应该只将它用于非常短的任务,因为如果它运行很长时间,它可能意味着它会错过下一个间隔?
如果您使用普通的旧 TThread,则不会出现问题。我想我在这里得到的是使用 TThread 或 TTimer 有效,但使用在 VCL 之外创建的线程不起作用,这就是为什么我问 C++ Builder 使用 VCL/Delphi RTL 的方式是否存在差异。
顺便说一句,前面提到的这段代码也失败了(但需要更长的时间),过了一会儿, CS := TCriticalSection.Create;
CS.Acquire;
for i := 1 to LoopCount do
begin
FormatDateTime('yyyy', 0, FormatSettings);
end;
CS.Release;
现在对于我真的不明白的一点,我按照建议写了这个;
function ReturnAString: string;
begin
Result := 'Test';
UniqueString(Result);
end;
然后在每种类型的计时器内,代码是;
for i := 1 to 10000 do
begin
ReturnAString;
end;
这会导致相同类型的故障,正如我之前所说,故障永远不会出现在 CPU 窗口内的同一个地方等。有时它是访问冲突,有时它可能是无效的指针操作。我正在使用 Delphi 2009 顺便说一句。
更新 2:
Roddy(下)指出 Ontimer 事件(不幸的是 Winsock,即 TClientSocket)使用 Windows 消息泵(顺便说一句,使用 IOCP 和重叠 IO 有一些不错的 Winsock2 组件会很好),因此推动离开从中。但是,有谁知道如何查看 CreateQueueTimerQueue 上设置了哪种线程本地存储?
感谢您花时间思考和回答这个问题。