Multithreading 具有超时的WaitForSingleObject在超时很久之后返回

Multithreading 具有超时的WaitForSingleObject在超时很久之后返回,multithreading,delphi,winapi,Multithreading,Delphi,Winapi,这几乎是一段遗留代码,包含一个简单的TThread,用作计时器,基于WaitForSingleObject()和事件句柄,如下所示 TTimerThread = class(TThread) private FInterval: cardinal; FEvent: THandle; FSomeClass: TSomeClass; protected procedure Execute; override; end; .... procedure TTimerThread.Ex

这几乎是一段遗留代码,包含一个简单的TThread,用作计时器,基于WaitForSingleObject()和事件句柄,如下所示

TTimerThread = class(TThread)
private
  FInterval: cardinal;
  FEvent: THandle;
  FSomeClass: TSomeClass;
protected
  procedure Execute; override;
end;

....

procedure TTimerThread.Execute;
var res: cardinal;
begin
  repeat
    log('Start WaitForSingleObject() with %d', [FInterval]);
    res := WaitForSIngleObject(FEvent, FInterval);
    log('End WaitForSingleObject() with result %d', [res]);
    if res = WAIT_TIMEOUT then
      if not Terminated then
        Synchronize(FSomeClass.SomeMethod);
  until Terminated;
end;
在某些特定于应用程序的故障检查(不触发时)和日志记录方面,代码有点精简

日志调用将显示在日志文件中,如下所示:

2016/11/12 17:49:08:056 $1130 llDebug Start WaitForSingleObject() with 20
2016/11/12 17:49:09:015 $1130 llDebug End WaitForSingleObject() with result 258
Log函数以格式打印NOW的值,$1130是当前线程,llDebug是日志级别。这两次调用之间没有记录任何内容(日志文件根据“功能”/“模块”提供)

在这种情况下,等待的时间是惊人的959毫秒

FEvent成员在主线程中创建(就像线程计时器本身一样),如下所示:

因此线程本身不创建窗口,也不使用COM或类似的东西。如果SomeMethod将使用这样的方法,那么它将在一个同步调用中执行,以便在主线程中执行。然而,对于这个特殊的测试,SomeMethod只是利用了一个时间

代码计算FInterval为20毫秒。线程大约每30/31毫秒触发一次,很可能是由于windows计时器分辨率

我们有一个客户运行windows 10,其中WaitForSingleObject()偶尔(相隔几分钟)会在400+毫秒后返回

SomeMethod在1ms内执行,因为它不做太多处理

我们不需要高分辨率的计时器,因为当前代码在其他任何地方都可以正常工作,每30毫秒一次就足够了,即使有10-15毫秒的“错误”

计时器控制一系列操作,这就是为什么它以大约20毫秒的间隔执行,但是对于这个问题,我们(明确地)排除了所有其他操作,只剩下一个操作在运行,这就是为什么我们能够调试它,并看到WaitForSingleObject()在400+毫秒后每隔几分钟不返回一次

WaitForSingleObject()前面有一个日志调用,后面有一个日志调用(还记录了间隔),因此100%确定WaitForSingleObject()在400毫秒以上返回,即使间隔是20毫秒。 日志记录显示WaitForSingleObject()的返回值为WAIT_TIMEOUT,如预期的那样

问题是:是什么导致WaitForSingleObject()中出现这种行为? 我的意思是,我可以理解,由于CPU忙,线程太多(在这个应用程序中不是这种情况),而在峰值负载低于30%的系统上,几乎半秒的时间是很奇怪的


感谢

考虑到问题中的日志调用,在计时器线程的
Execute
过程中,如果它们最终在同一线程中写入日志文件,这将阻塞,直到写入操作完成。由于开始时间是在第一次写入操作之前检索的,因此日志间隔不仅包括等待时间(
WaitForSingleObject
),还包括第一次写入操作。这可以解释一般偏向30毫秒而不是20毫秒的原因。不正常的I/O子系统或拥塞也可以解释偶尔延长的时间段

检索等待操作之前的开始时间,但不要写入它。在等待调用返回后写入开始和结束时间。然后,日志将更准确地反映等待时间,并且最有可能的是,延长的延迟将被证明是I/O限制的。
WaitForSingleObject
不太可能不准确

还考虑将日志写入卸载到工作线程。

FEvent := CreateEvent(nil, false, false, nil);