【发布时间】:2020-01-08 23:19:00
【问题描述】:
我正在开发一个 MFC 应用程序(在 Win10 下运行),它包括一个图形 CAD 样式的编辑器窗口。编辑器窗口包含用户可以重新定位和配置的图标。
在包含许多元素的布局中,我们发现编辑器窗口最多可以挂起 30 秒。当我们的大多数用户使用 Windows 7 时,未报告此问题; Windows 10 似乎开始发生这种情况,但我还没有回到 Win7 确认。
触发挂起的确切动作顺序是:
- 水平滚动(使用水平滚动条)
- 垂直滚动(使用垂直滚动条)
- 应用程序挂起大约 20-30 秒,然后恢复
上述步骤的微小变化也会触发临时挂起;例如,使用鼠标按钮垂直滚动,然后使用滚动条水平滚动,也会触发挂起。
挂起或锁定总是会恢复。我还注意到 Windows 中的其他应用程序暂时“冻结”(例如:我无法拖动窗口,并且 UI 更新变得非常缓慢,对于在此挂起时运行的 所有 应用程序) .
我不确定从哪里开始调试,因为我感觉这是在操作系统级别发生的;我的第一个猜测是开始分析不同的代码行以针对导致延迟的确切操作系统调用,但我不确定如果导致挂起的元素不是我的函数调用代码;例如,可能某个队列在操作系统中几乎已满,导致消息泵变慢,而没有任何特定的操作系统调用看起来很慢。
我的问题是:
- Windows 10 w.r.t 中是否有任何变化?较大的 CWnd 计数,这可能与滚动交互导致挂起?
- Windows 为调试此方案提供了哪些工具?我应该看WinDbg吗?我是否应该专注于分析问题而不使用任何专用调试工具?
我将从内部应用程序的角度来解决这个问题(以排除我们的代码直接导致挂起的可能性),但如果我们能提供有关调试此问题的最佳方法的任何指导,我将不胜感激在我们的代码中没有像 30 秒函数调用这样明显的东西。
感谢 cmets。以下是一些新信息:
- 此事件期间 CPU 使用率较低;徘徊在 1-3% 左右,所以我的代码没有受到 CPU 的瓶颈。
- 我在 HSCROLL 和 VSCROLL 处理程序的入口点和出口点都添加了 TRACE 语句。挂起似乎发生在在进入我的 VSCROLL 处理程序之前,在我用鼠标左键单击滚动条之后立即发生。
- 该代码有一个 LButtonDown 处理程序,但在单击滚动条时似乎没有被点击
- 该应用程序有 208 个 GDI 对象和 66 个用户对象,所以我认为我们远远低于限制
- 在所有测试过的 Win10 电脑上都观察到此问题,并非单台机器独有
现在要试试 Spy++。
我没有看到处理程序有任何明显的问题,我的调试输出似乎排除了它们是罪魁祸首,但这里它们是为了完整性:
void CDrawing60View::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
TRACE("OnHScroll:Begin\r\n");
int i = 1;
switch ( nSBCode )
{
case SB_LEFT : // Scroll to far left.
i = 2 ;
break ;
case SB_ENDSCROLL : // End scroll.
i = 3 ;
break ;
case SB_LINELEFT : // Scroll left. left arrow on left side of scroll bar
i = 4 ;
break ;
case SB_LINERIGHT : // Scroll right. right arrow on right side of scroll bar
i = 5 ;
break ;
case SB_PAGELEFT : // Scroll one page left.
i = 6 ;
break ;
case SB_PAGERIGHT : // Scroll one page right.
i = 7 ;
break ;
case SB_RIGHT : // Scroll to far right.
i = 8 ;
break ;
case SB_THUMBPOSITION : // Scroll to absolute position. The current position is specified by the nPos parameter.
i = 9 ;
break ;
case SB_THUMBTRACK : // Drag scroll box to specified position.
i = 10;
break ;
}
CFormView::OnHScroll(nSBCode, nPos, pScrollBar);
CPoint p = GetScrollPosition(); // p = how much we have scrolled in the horizontal/vertical directions
SNAP_TO_8_PIXELS (p.x);
SNAP_TO_8_PIXELS ( p.y)
ScrollToPosition ( p ) ;
MoveDrawing . LastScrollPositionX = p . x ; // used when saving the drawing
MoveDrawing . LastScrollPositionY = p . y ;
TRACE("OnHScroll:End\r\n");
}
void CDrawing60View::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
TRACE("OnVScroll:Begin\r\n");
int i = 0;
switch ( nSBCode )
{
case SB_BOTTOM : // Scroll to bottom.
i = 2 ;
break ;
case SB_ENDSCROLL : // End scroll.
break ;
case SB_LINEDOWN : // Scroll one line down.
i = 2 ;
break ;
case SB_LINEUP : // Scroll one line up.
i = 2 ;
break ;
case SB_PAGEDOWN : // Scroll one page down.
i = 2 ;
break ;
case SB_PAGEUP : // Scroll one page up.
i = 2 ;
break ;
case SB_THUMBPOSITION : // Scroll to the absolute position. The current position is provided in nPos.
i = 2 ;
break ;
case SB_THUMBTRACK : // Drag scroll box to specified position. The current position is provided in nPos.
i = 2 ;
break ;
case SB_TOP : // Scroll to top.
i = 2 ;
break ;
}
CFormView::OnVScroll(nSBCode, nPos, pScrollBar);
CPoint p = GetScrollPosition(); // p = how much we have scrolled in the horizontal/vertical directions
SNAP_TO_8_PIXELS (p.x);
SNAP_TO_8_PIXELS ( p.y)
ScrollToPosition ( p ) ;
MoveDrawing . LastScrollPositionX = p . x ; // used when saving the drawing
MoveDrawing . LastScrollPositionY = p . y ;
TRACE("OnVScroll:End\r\n");
}
好的,Spy++ 产生了一些有趣的结果。当我运行 Spy++ 时,我无法重现此问题!。我想知道这是否表明我的处理程序中存在竞争条件,因为我可以想象 Spy++ 的唯一效果是减慢速度。
【问题讨论】:
-
>>我还注意到 Windows 中的其他应用程序暂时“冻结”(例如:我无法拖动窗口,UI 更新变得非常缓慢,对于所有应用程序发生此挂起时运行)。 这可能意味着您的应用占用了大量 CPU 时间,请查看任务管理器中的 CPU 使用情况。如果是这样,请调查代码中的哪个函数导致了这种情况。
-
也许考虑发布(部分)处理滚动请求的代码:
HScroll和VScroll处理程序之间可能会发生无意的数据竞争。 -
我没有在 Windows 10 中看到这个冻结问题。您的 Windows 设置可能有问题,或者您正在运行资源密集型应用程序。使用任务管理器检查您的应用程序使用了多少 GDI 句柄,或者其他应用程序是否正在使用资源。
-
某些 Windows 后台任务(Defender、Cortana、用户体验等)确实会导致 UI 延迟并使系统看起来像死机一样。也许检查是否有任何这些干预并给您带来麻烦。至于应用程序,上面@AdrianMole 的评论可能就是答案。使用跟踪(不是调试)来检查代码的某些部分是否被一次又一次地调用。在 H/VScroll 事件处理程序中会发生什么?在那里做一些 CPU 或图形密集型操作?还要检查计算或设置可滚动区域大小的代码。
-
窗口计数限制没有变化。限制是相同的:10,000 个 USER 对象,其中一个窗口是 USER 堆中的 USER 对象。您可以在详细视图中使用任务管理器来查看 USER 对象计数。您必须在详细视图中添加该列,因为默认情况下它不显示 USER 对象计数。