可等待的计时器不等待指定的时间段

Waitable timer not waiting for specified time period

我正在创建一个手动等待计时器,如下所示

m_hTimer = CreateWaitableTimer(NULL, true, NULL);

并在函数中使用它

HRESULT ClassA::InduceSleep(UINT32 uiMiliSeconds)
{
    if (m_hTimer)
    {
        LARGE_INTEGER liDueTime;
        liDueTime.QuadPart = (uiMiliSeconds) * (-10000) * (1LL);
        if (!SetWaitableTimer(m_hTimer, &liDueTime, 0, NULL, NULL, 0))
        {
            Log("SetWaitableTimer failed GLE[%d]", GetLastError());
            goto exit;
        }

        // Wait for the timer.

        if (WaitForSingleObject(m_hTimer, INFINITE) != WAIT_OBJECT_0)
        {
            Log("WaitForSingleObject failed GLE[%d]", GetLastError());
        }
        return S_OK;
    }
exit:    
    Sleep(uiMiliSeconds);
    return S_OK;
}

我观察到在延迟 10 秒(或 5 秒)调用 InduceSleep() 时,waitforsingleobject 立即返回 WAIT_OBJECT_0,没有任何延迟,因此计时器会立即发出信号。文档中提到 setwaitable timer 停止并重新激活计时器,因此它不应处于信号状态,而应仅在给定时间后发出信号。我在这里错过了什么?

这是一个简单的错误:

uiMiliSecondsUINT32 类型,即无符号类型。

这使得

(uiMiliSeconds) * (-10000)

无符号乘法,即 -10000 在乘法之前转换为 unsigned。 (我同意,类型提升有时是一个棘手的话题。)

我在最小样本中尝试了这个:

#include <iostream>

int main()
{
  uint32_t uiMilliseconds = 10 * 1000;
  std::cout << uiMilliseconds * -10000 << '\n';
  std::cout << (int)uiMilliseconds * -10000 << '\n';
  return 0;
}

输出:

4194967296
-100000000

Live Demo on coliru

因此,解决方案是在乘法之前简单地将 uiMiliseconds 转换为有符号整数(就像我在第二行输出中所做的那样)。

故事的其余部分对 OP 来说可能是显而易见的。来自 MSDN 关于 SetWaitableTimer function:

lpDueTime

The time after which the state of the timer is to be set to signaled, in 100 nanosecond intervals. Use the format described by the FILETIME structure. Positive values indicate absolute time. Be sure to use a UTC-based absolute time, as the system uses UTC-based time internally. Negative values indicate relative time.

所以,错误的计算时间似乎产生了一个已经过去的值。这是为什么 SetWaitableTimer(m_hTimer, &liDueTime, 0, NULL, NULL, 0) 立即 returns 的唯一合理解释。 (OP 检查错误但没有识别出错误。)


要做到"bullet-proof",我会推荐

 uiMiliSeconds * -10000LL

考虑到例如VC 对 int 使用 32 位(即使是 x64)。因此,由于类型提升,任何 UINT32 值都会扩展为相应的 long long 而没有溢出的危险。


RbMm 抱怨说 MiliSeconds 的类型 UINT32 可能是一个不太幸运的选择,因为它只涵盖了可以设置的时间子范围。在 OP 的情况下,这个子范围可能就足够了。否则,可能会考虑输入 UINT64