具有超时的WaitForSingleObject在超时后返回很长时间

时间:2016-11-12 11:12:27

标签: multithreading delphi winapi

有这个几乎遗留的代码,涉及一个简单的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是loglevel。这两个调用之间没有任何记录(日志文件是每个“功能”/“模块”)

在这种情况下,等待时间高达959毫秒!?

FEvent成员是在主线程中创建的(就像线程计时器本身一样):

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

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

代码计算FInterval为20 ms。线程大约每隔30/31 ms触发一次,很可能是由于Windows计时器分辨率造成的。

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

SomeMethod在1毫秒内执行,因为它没有做太多处理。

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

定时器控制一堆操作,这就是为什么它以大约20ms的间隔执行,但是对于这个问题,我们已经消除了所有其他的(显式)并且只剩下1个操作运行,这就是我们如何调试它并看到WaitForSingleObject()每隔几分钟就会在400+ ms后返回。

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

问题是:在WaitForSingleObject()中可能导致此行为的原因是什么? 我的意思是,由于CPU忙,太多线程(在这个应用程序中不是这种情况),我可以理解额外几毫秒,但在峰值负载低于30%的系统上几乎半秒是奇怪的。

由于

1 个答案:

答案 0 :(得分:-1)

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

检索等待操作之前的开始时间,但不要写入它。在等待调用返回后写入开始和结束时间。然后日志将更准确地反映等待时间,并且很可能延长的延迟将变为I / O限制。 <{1}}不太可能不准确。

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