【问题标题】:Waitable timer not waiting for specified time period等待计时器未等待指定时间段
【发布时间】:2019-02-21 05:50:32
【问题描述】:

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

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 计时器停止并重新激活计时器,因此它不应处于信号状态,并且应仅在给定时间后发出信号。我在这里错过了什么?

【问题讨论】:

  • 出于好奇(但可能跑题了):为什么要(-10000) * (1LL)?为什么不-10000LL?你调用函数时uiMilliSeconds 的值是多少?
  • 顺便说一句。关于(-10000) * (1LL) 的问题还不错。 ;-) LL 生成一个 long long 常量,它的字节数比 UINT32 多。这反转了类型提升并导致了 long long 乘法 (uiMiliseconds * -10000LL),即将 uiMiliSeconds 转换为 long long 并解决了您的问题。
  • 我们需要 liDueTime.QuadPart = (LONGLONG)uiMiliSeconds * -10000LL 代码 - 首先必须将 uiMiliSeconds 扩展为 64 位(它将是零扩展,因为 uiMiliSeconds 是无符号的,这没关系)然后在 -10000LL 上进行 mult( 0xFFFFFFFFFFFFD8F0)。在您的代码中,您在 FFFFD8F0h (-10000) 上 mult UINT32 并得到非常小的 值 - 绝对时间接近 1600 年

标签: c++ windows winapi msdn


【解决方案1】:

这是一个简单的错误:

uiMiliSeconds 属于 UINT32 类型,即无符号类型。

这让

(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

计时器状态设置为已发出信号的时间,以 100 纳秒为间隔。使用 FILETIME 结构描述的格式。正值表示绝对时间。请务必使用基于 UTC 的绝对时间,因为系统内部使用基于 UTC 的时间。负值表示相对时间。

因此,错误的计算时间似乎产生了一个已经过去的值。这是SetWaitableTimer(m_hTimer, &amp;liDueTime, 0, NULL, NULL, 0) 立即返回的唯一合理解释。 (OP 检查了错误,但没有识别出来。)


为了使其“防弹”,我建议

 uiMiliSeconds * -10000LL

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


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

【讨论】:

  • 真的需要 unsigned expand uiMiliseconds 到 64 位之前的多个:(unsigned __int64)uiMiliseconds * -1000
  • 最终结果为 64bit 值。如果多个表达式的第一个参数是 32 位(有符号或无符号无关),我们可以从某个值开始溢出
  • @RbMm 一般来说,你是对的。在 5 秒或 10 秒的 OP 的具体问题中,这个最多是 10000 * -10000。因此,溢出不是这里的问题。
  • 是的,我们需要负值。但我要说另一个,如果等待间隔会很大 - 如果不先将其扩展到 64 位,我们就会过低
猜你喜欢
  • 2014-12-29
  • 2021-03-25
  • 1970-01-01
  • 2019-03-11
  • 2021-04-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多