Delphi Win API CreateTimerQueueTimer线程和线程安全的FormatDateTime崩溃

时间:2008-12-09 12:05:21

标签: multithreading delphi winapi formatdatetime

这是一个很长的问题,但我们走了。有一个版本的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结构中的Locale信息(我通过单步执行代码仔细检查)。

我提到没有任何分配明确表示没有访问共享存储,因此不需要锁定。

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 btw。

更新2:

Roddy(下面)指出Ontimer事件(不幸的是也是Winsock,即TClientSocket)使用windows消息泵(除了使用IOCP和重叠IO有一些不错的Winsock2组件会很好),因此推动摆脱它。但是有谁知道如何在CreateQueueTimerQueue上设置什么类型的线程本地存储?

感谢您花时间思考并回答这个问题。

8 个答案:

答案 0 :(得分:5)

我不确定在我自己的问题上发表“答案”是不是一个好的形式,但它似乎合乎逻辑,让我知道这是不是很酷。

我想我已经找到了问题,线程本地存储的想法引导我跟随一堆线索,我发现了这条神奇的线条;

IsMultiThread:= True;

来自帮助;

“IsMultiThread设置为true表示内存管理器应该支持多个线程.IllMultiThread由BeginThread和类工厂设置为true。”

当然,使用TTimer使用单个主VCL线程设置,但是在使用TThread时为您设置。如果我手动设置它,问题就会消失。

在C ++ Builder中,我不使用TThread,但它使用以下代码显示;

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

它会自动为你设置。

我很高兴人们的投入,以便我能找到这一点,我希望它可以帮助别人。

答案 1 :(得分:1)

由于FormatDateTime调用的DateTimeToString使用GetThreadLocale,您可能希望为每个线程尝试使用本地FormatSettings变量,甚至可以在循环之前在局部变量中设置FormatSettings。

它也可能是导致此问题的WT_EXECUTEINTIMERTHREAD参数。请注意,它声明它只应用于非常短的任务。

如果问题仍然存在,问题实际上可能在其他地方,这是我第一次看到这个时的预感,但我没有足够的信息来确定代码的确如此。

有关发生访问冲突的位置的详细信息可能有所帮助。

答案 2 :(得分:1)

您确定这实际上与FormatDateTime有关吗?你提到那里没有任何转让声明;这是你问题的一个重要方面吗?如果你调用其他字符串返回函数会发生什么? (确保它不是一个常量字符串。编写自己的函数,在返回之前调用UniqueString(Result)。)

FormatSettings变量是否特定于线程?这就是为FormatDateTime提供额外参数的重点,因此每个线程都有自己的私有副本,保证在函数处于活动状态时不被任何其他线程修改。

计时器队列对这个问题很重要吗?或者,当您使用普通的TThread并在Execute方法中运行循环时,您会得到相同的结果吗?

你确实警告说这是一个很长的问题,但似乎你可以采取一些措施使它变小,以缩小问题的范围。

答案 3 :(得分:0)

我想知道您正在进行的RTL / VCL调用是否期望访问某些线程局部存储(TLS)变量,这些变量在您通过定时器队列调用代码时未正确设置?

这不是您的问题的答案,但是您是否知道TTimer OnTimer事件只是作为主VCL线程中正常消息循环的一部分运行?

答案 4 :(得分:0)

你找到了答案 - IsMultiThread。必须随时使用它来恢复使用API​​并创建线程。从MSDN:CreateTimerQueueTimer创建一个线程池来处理这个功能,所以你有一个外部线程使用主VCL线程没有保护。 (注意:除非代码的其他部分尊重此锁,否则您的CS.acquire / release根本不会执行任何操作。)

答案 5 :(得分:0)

重新。关于Winsock和重叠I / O的最后一个问题:您应该仔细查看Indy

Indy使用阻塞I / O,当您需要高性能网络IO而不管主线程在做什么时,它是一个很好的选择。现在你已经解决了多线程问题,你应该创建另一个线程(或更多)来使用indy来处理你的I / O.

答案 6 :(得分:0)

Indy的问题在于,如果你需要很多连接,它根本就没有效率。它需要每个连接一个线程(阻塞I / O),它根本不能扩展,因此IOCP和重叠IO的好处,它几乎是Windows上唯一可扩展的方式。

答案 7 :(得分:0)

对于update2:

有一个免费的IOCP套接字组件:http://www.torry.net/authorsmore.php?id=7131(包含源代码)

  

“作者:Naberegnyh Sergey N ..高   性能套接字服务器基于   Windows完成端口和使用   Windows套接字扩展。 IPv6的   支持的。 “

我找到了它,同时寻找更好的组件/库来重新架构我的小即时消息服务器。我还没有尝试过,但它看起来很好,只是第一印象。