在图形应用程序中使用计时器时的系统总冻结

时间:2011-02-04 21:17:10

标签: winapi timer

我真的很困惑这个问题,非常感谢任何建议。

问题: 我们的一些用户抱怨使用我们的产品时整个系统“冻结”。无论我们如何尝试,我们都无法在任何可用于故障排除的系统中重现它。

产品 从物理上讲,它是一个32位/ 64位DLL。该产品具有自刷新的GUI,可绘制音频信号的实时频谱图

问题详情: 我从一些零碎的报告中收集到的信息如下:

当GIU打开时,有时会立即打开,有时候在看到几分钟的GIU后,系统会完全失速,无法操作窗口,启动任务管理器等。键盘没有反应,没有看到鼠标光标(或者是看到但不负责鼠标移动 - 这我不知道。用户必须硬重置系统才能重新启动。我认为重要的是,在某些情况下(在某些情况下)GIU具有响应性,并显示一些足够的图片。然后这种冻结发生了。其中一个报告告诉我们,一旦系统被冻结,音频将继续呈现 - 即由记者听到(但Windows的整个图形外壳已经被冻结)。注意:在这种应用程序中,它通常是一个负责声音处理的专用线程。

对于使用32位和64位版本的DLL的Windows7 x64上的2个用户,或多或少确认会发生冻结,从未听说过与此冻结相关的任何其他操作系统(尽管有1个报告没有任何操作系统)指定)。

这就是我设法收集的全部内容。

架构/怀疑:

我强烈怀疑这是GUI刷新周期的罪魁祸首。

基本上,它的工作原理如下:

  1. 有一个定时器以大约25 fps的帧速率触发回调。
  2. 在此回调中,执行音频分析并更新GUI
  3. 有关计时器的一些细节:

    这是基于这个电话:

    CreateTimerQueueTimer(&m_timerHandle, NULL, xPlatformTimerCallbackWrapper,
           this, m_firstExpInterval, m_period, WT_EXECUTEINTIMERTHREAD);
    

    我们创建一个计时器,并定期调用m_timerHandle

    有关GUI刷新的一些细节:

    它的工作原理如下:

    HDC hdc = GetDC (hwnd);
    // Some drawing
    ReleaseDC(hwnd,hdc);
    

    我的直觉告诉我,这CreateTimeQueueTimer可能不是正确的决定。 reference page告诉我使用WT_EXECUTEINTIMERTHREAD

      

    回调函数由计时器线程本身调用。这面旗帜   应该只用于短期任务或   它可能影响其他计时器   操作。回调函数是   排队作为APC。它不应该   执行可警告的等待操作。

    我不记得为什么实际选择了这个WT_EXECUTEINTIMERTHREAD选项,现在WT_EXECUTEDEFAULT似乎同样适合我。

    事实上,我没有看到使用参考页面中提到的任何选项有任何重大差异。

    问题:

    1. 是什么告诉任何人任何可能出错的线索?
    2. 你遇到过类似的问题,原因是什么?
    3. 感谢您的任何信息!

      ==========================================

      更新:2010-02-20

      不幸的是,这里给出的建议(到目前为止我可以检查)没有帮助,名字:

      • CreateTimerQueueTimer(&m_timerHandle,NULL,xPlatformTimerCallbackWrapper,this,m_firstExpInterval,m_period, WT_EXECUTEDEFAULT);
      • 中更改为WT_EXECUTEDEFAULT
      • reenterability guard已经存在
      • 我尚未检查是否更新了WM_PAINT hander中的GUI有帮助

      无论如何,谢谢你的提示。

      现在,我已经玩了一段时间,还有一个真正的W7安装(我以前使用虚拟的),似乎问题可以缩小。

      在我的安装中,使用应用程序确实让GUI响应性降低,尽管我无法像某人报告的那样设法重现整个系统的冻结。

      我现在的假设是这种反应性降低和报告的总冻结有一个共同的起源。

      然后我做了一些原始的分析,发现至少有一个罪魁祸首是 BitBlt 函数,每秒调用约50次

      BitBlt ((HDC)pContext->getSystemContext (),  // hdcDest
                      destRect.left + pContext->offset.h, 
                      destRect.top + pContext->offset.v,  
                      destRect.right - destRect.left,    
                      destRect.bottom - destRect.top,    
      
                      (HDC)pSystemContext,                
                      srcOffset.h,                    
                      srcOffset.v,           
                      SRCCOPY);           
      

      复制的区域不是很大(大约400x200像素)。它用于显示后备缓冲区,并在定时器回调中执行。

      如果我注释掉这个BitBlt调用,问题似乎消失了(至少部分)。

      在运行WinXP的同一台机器上,一切正常。

      有关于此的任何想法吗?

2 个答案:

答案 0 :(得分:2)

最有可能发生的事情是你的计时器回调执行时间超过25毫秒。然后另一个计时器滴答,也开始处理。等等,很快就会有一大堆线程吸收CPU周期,所有人都试图进行音频分析,而且在短时间内系统忙于进行线程上下文切换,没有真正的工作完成。而且一直以来,越来越多的计时器滴答被放入队列中。

强烈建议你在这里使用WT_EXECUTEDEFAULT,而不是WT_EXECUTEINTIMERTHREAD。此外,您需要防止重叠的计时器回调。有几种方法可以做到这一点。

您可以在计时器回调中使用关键部分。当回调被触发时,它会调用TryEnterEnterCriticalSection,如果不成功,则只返回而不做任何事情。

您可以使用volatile变量和InterlockedCompareExchange来执行类似操作。

或者,您可以将计时器更改为一次性(WT_EXECUTEONLYONCE),然后在每次回调结束时重新设置计时器。这将使事情在最后一次完成后执行25 ms。

您选择的是由您自己决定的。如果您的分析通常需要超过25毫秒但不超过35毫秒,那么您可能会使用WT_EXECUTEONLYONCE获得更平滑的更新速率。如果分析时间超过25毫秒,或者通常需要超过35毫秒(但小于50毫秒),那么您最好使用其他技术之一。

当然,如果它通常需要超过25毫秒,那么您可能希望增加时间(降低更新速率)。

另外,正如其中一位评论者指出的那样,问题还可能涉及从计时器线程访问GUI。您应该在计时器线程中进行所有分析,将结果存储在主线程可以访问它的位置,然后向窗口proc发送消息,告诉它更新显示。

答案 1 :(得分:0)

您是否曾要求用户停用Aero / WDM DWM?启用Aero后,渲染实现完全不同。没有Aero,行为将类似于XP。并不是说它能解决任何问题,但它会让你知道问题是什么。