这是一个有点长的问题,但是我们来说一下。有一个被称为线程安全的FormatDateTime版本,您可以使用它来格式化日期和时间。线程安全意味着多个线程可以同时调用该函数而不会导致任何问题。这是通过将每个线程的数据存储在其自己的栈帧中来实现的。
GetLocaleFormatSettings(3081, FormatSettings);
获取一个值,然后您可以像这样使用它;
FormatDateTime('yyyy', 0, FormatSettings);
现在想象一下两个计时器,一个使用TTimer(间隔为1000ms),然后另一个计时器创建如下(10ms间隔);
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作为常量传递,因此如果它们使用相同的副本是否重要(事实证明,在函数调用中传递本地版本会产生相同的问题)?另外,采用FormatSettings的版本不调用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上设置了什么样的线程本地存储吗?
感谢您抽出时间思考并回答这个问题。