"创建窗口句柄时出错"

use*_*133 27 .net handles winforms

我们正在开发一个非常大的.NET WinForms复合应用程序 - 不是CAB,而是一个类似的本土框架.我们正在运行在Windows Server 2003上的Citrix和RDP环境中运行.

我们开始遇到随机和难以复制的"错误创建窗口句柄"错误,这似乎是我们的应用程序中的旧时尚句柄泄漏.我们正在大量使用第三方控件(Janus GridEX,Infralution VirtualTree和.NET Magic对接),我们根据数据库中的元数据进行了大量动态加载和内容呈现.

谷歌有很多关于这个错误的信息,但没有很多关于如何避免这方面问题的可靠指导.

stackoverflow社区是否对我构建易于操作的winforms应用程序有任何良好的指导?

Jac*_*ing 29

我已经跟踪了很多问题,因为在WinForms中没有像预期的那样卸载UI.

以下是一些一般提示:

  • 很多时候,控件将继续使用,因为控件事件没有被正确删除(工具提示提供程序在这里导致我们真正的大问题)或控件没有正确处理.
  • 在所有模态对话框周围使用"使用"块以确保它们是Disposed
  • 有一些控件属性会在必要之前强制创建窗口句柄(例如,设置TextBox控件的ReadOnly属性将强制实现控件)
  • 使用像.Net Memory profiler这样的工具来获取所创建的类的计数.此工具的较新版本还将跟踪GDI和USER对象.
  • 尽量减少使用Win API调用(或其他DllImport调用).如果确实需要使用interop,请尝试以这样的方式包装这些调用,即using/Dispose模式将正常工作.


Jer*_*son 10

理解这个错误

突破 Windows 的限制:USER 和 GDI 对象 – Mark Russinovich 的第 1 部分:https : //blogs.technet.microsoft.com/markrussinovich/2010/02/24/pushing-the-limits-of-windows-user-and -gdi-objects-part-1/

解决此错误

您需要能够重现该问题。这是记录执行此操作的步骤的一种方法/sf/answers/2136817021/

找出产生这么多句柄的最简单方法是打开 TaskMgr.exe。在 TaskMgr.exe 中,您需要如图所示显示 USER Object、GDI Object 和 Handles 列,为此选择 View Menu > Select Columns:

在此处输入图片说明

执行导致问题的步骤并观察 USER 对象计数增加到 10,000 左右或 GDI 对象或句柄达到其限制。

当您看到对象或句柄增加(通常显着增加)时,您可以通过单击暂停按钮来暂停 Visual Studio 中的代码执行。

然后只需按住 F10 或 F11 即可浏览代码,观察对象/句柄计数急剧增加时的情况。

迄今为止我发现的最好的工具是 NirSoft 的 GDIView,它分解了 GDI 句柄字段:

在此处输入图片说明

我将其追溯到设置 DataGridViews“过滤器组合框”列位置和宽度时使用的代码:

If Me.Controls.ContainsKey(comboName) Then
    cbo = CType(Me.Controls(comboName), ComboBox)
    With cbo
        .Location = New System.Drawing.Point(cumulativeWidth, 0)
        .Width = Me.Columns(i).Width
    End With
    'Explicitly cleaning up fixed the issue of releasing USER objects.
    cbo.Dispose()
    cbo = Nothing  
End If
Run Code Online (Sandbox Code Playgroud)

在我的情况下(以上),解决方案是明确处置和清理解决了释放 USER 对象的问题。

这是堆栈跟踪:

在 System.Windows.Forms.Control.CreateHandle() 在 System.Windows.Forms.ComboBox.CreateHandle() 在 System.Windows.Forms.Control.get_Handle() 在 System.Windows.Forms.ComboBox.InvalidateEverything() 在 System .Windows.Forms.ComboBox.OnResize(EventArgs e) 在 System.Windows.Forms.Control.OnSizeChanged(EventArgs e) 在 System.Windows.Forms.Control.UpdateBounds(Int32 x, Int32 y, Int32 width, Int32 height, Int32 clientWidth, Int32 clientHeight) 在 System.Windows.Forms.Control.UpdateBounds(Int32 x, Int32 y, Int32 width, Int32 height) 在 System.Windows.Forms.Control.SetBoundsCore(Int32 x, Int32 y, Int32 width, Int32 height) , BoundsSpecified 指定)在 System.Windows.Forms.ComboBox.SetBoundsCore(Int32 x, Int32 y, Int32 width, Int32 height, BoundsSpecified specified) at System.Windows.Forms.Control.SetBounds(Int32 x, Int32 y,Int32 宽度、Int32 高度、BoundsSpecified 指定)在 System.Windows.Forms.Control.set_Width(Int32 value)

这是Fabrice帮助我找出限制的有用文章的关键:

“创建窗口句柄
出错”当我正在为客户端工作的大型 Windows 窗体应用程序被积极使用时,用户经常会收到“创建窗口句柄时出错”的异常。

除了应用程序消耗太多资源这一事实之外,这是我们已经在解决的一个单独问题,我们很难确定哪些资源正在耗尽以及这些资源的限制是什么。我们首先考虑关注 Windows 任务管理器中的 Handles 计数器。那是因为我们注意到某些进程往往比正常情况消耗更多的这些资源。然而,这个计数器并不是一个好的计数器,因为它跟踪资源,如文件、套接字、进程和线程。这些资源被命名为内核对象。

我们应该关注的其他类型的资源是 GDI 对象和用户对象。您可以在 MSDN 上获得三类资源的概述。

用户对象
窗口创建问题与用户对象直接相关。

我们试图确定应用程序可以使用的用户对象的限制是多少。每个进程有 10,000 个用户句柄的配额。这个值可以在注册表中更改,但是在我们的例子中这个限制并不是真正的阻碍。另一个限制是每个 Windows 会话 66,536 个用户句柄。这个限制是理论上的。在实践中,您会注意到它无法访问。在我们的例子中,在当前会话中的用户对象总数达到 11,000 之前,我们得到了可怕的“错误创建窗口句柄”异常。

桌面堆
然后我们发现哪个限制是真正的罪魁祸首:它是“桌面堆”。默认情况下,交互式用户会话的所有图形应用程序都在名为“桌面”的地方执行。分配给此类桌面的资源是有限的(但可配置)。

注意:用户对象消耗了桌面堆的大部分内存空间。这包括窗户。有关桌面堆的更多信息,您可以参考 NTDebugging MSDN 博客上发表的非常好的文章:

真正的解决方案是什么?变绿!
增加桌面堆是一种有效的解决方案,但这不是最终的解决方案。真正的解决方案是消耗更少的资源(在我们的例子中更少的窗口句柄)。我可以猜到您对这个解决方案会有多失望。这真的是我能想到的全部吗??嗯,这里没有什么大秘密。唯一的出路就是瘦。拥有不那么复杂的 UI 是一个好的开始。这对资源有好处,对可用性也有好处。下一步是避免浪费,保护资源,并回收它们!

以下是我们如何在我客户的应用程序中执行此操作:

我们使用 TabControls 并在每个选项卡变得可见时动态创建它的内容;我们使用可扩展/可折叠区域,并仅在需要时再用控件和数据填充它们;我们尽快释放资源(使用 Dispose 方法)。当一个区域被折叠时,可以清除它的子控件。选项卡隐藏时也是如此;我们使用 MVP 设计模式,这有助于使上述成为可能,因为它将数据与视图分开;我们使用布局引擎、标准 FlowLayoutPanel 和 TableLayoutPanel 或自定义引擎,而不是创建嵌套面板、GroupBoxes 和 Splitter 的深层层次结构(一个空的拆分器本身消耗三个窗口句柄......)。如果您需要构建丰富的 Windows 窗体屏幕,以上只是提示您可以做什么。那里' 毫无疑问,您可以找到其他方法。在我看来,您应该做的第一件事是围绕用例和场景构建您的应用程序。这有助于仅显示给定时间和给定用户所需的内容。

当然,另一种解决方案是使用不依赖句柄的系统... WPF 任何人?


ade*_*esh 6

当我将NativeWindow子类化并手动调用CreateHandler时,我遇到了这个错误.问题是我忘了在我的覆盖版本的WndProc中添加base.WndProc(m).它导致了同样的错误


小智 5

我遇到了这个异常,因为无限循环创建了新的UI控件并设置了它的属性.在循环多次之后,当变更控制可见属性时抛出此excption.我发现用户对象和GDI对象(来自任务管理器)都非常大.

我想你的问题是类似的原因,系统资源被那些UI控件耗尽.