了解此错误
突破 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/
解决此错误
您需要能够重现该问题。这是记录执行该步骤的一种方法https://stackoverflow.com/a/30525957/495455。
找出造成这么多句柄的最简单方法是打开 TaskMgr.exe。在 TaskMgr.exe 中,您需要使 USER Object、GDI Object 和 Handles 列可见,如图所示,为此选择 View Menu > Select Columns:

完成导致问题的步骤并观察用户对象计数增加到大约 10,000 或 GDI 对象或句柄达到其限制。
当您看到对象或句柄增加(通常显着)时,您可以通过单击暂停按钮来停止 Visual Studio 中的代码执行。
然后只需按住 F10 或 F11 即可浏览代码,观察对象/句柄计数何时急剧增加。
到目前为止我发现的最好的工具是来自 NirSoft 的 GDIView,它分解了 GDI 句柄字段:

我将其追踪到设置 DataGridViews“Filter Combobox”列位置和宽度时使用的代码:
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
在我的情况下(上图),解决方案是明确地处理和清理解决了释放 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 宽度, Int32 高度, 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 宽度, Int32 高度, BoundsSpecified 指定) 在 System.Windows.Forms.Control.SetBounds(Int32 x, Int32 y,Int32 宽度,Int32 高度,BoundsSpecified 指定)在 System.Windows.Forms.Control.set_Width(Int32 值)
以下是Fabrice的一篇有用文章的症结所在,该文章帮助我解决了限制:
“创建窗口句柄时出错”
当我正在为客户端开发的大型 Windows 窗体应用程序被积极使用时,用户经常会收到“创建窗口句柄时出错”异常。
除了应用程序消耗过多资源(这是我们已经在解决的一个单独的问题)这一事实之外,我们还难以确定哪些资源正在耗尽以及这些资源的限制是什么。我们首先考虑关注 Windows 任务管理器中的 Handles 计数器。那是因为我们注意到某些进程倾向于消耗比正常情况下更多的这些资源。然而,这个计数器并不是一个好的计数器,因为它会跟踪文件、套接字、进程和线程等资源。这些资源被命名为内核对象。
我们应该关注的其他类型的资源是 GDI 对象和用户对象。您可以在 MSDN 上获得这三类资源的概览。
用户对象
窗口创建问题与用户对象直接相关。
我们试图确定应用程序可以使用的用户对象的限制。每个进程有 10,000 个用户句柄的配额。这个值可以在注册表中更改,但是在我们的例子中,这个限制并不是真正的阻碍。另一个限制是每个 Windows 会话 66,536 个用户句柄。这个限制是理论上的。在实践中,您会注意到它无法到达。在我们的例子中,在当前会话中的用户对象总数达到 11,000 个之前,我们遇到了可怕的“创建窗口句柄时出错”异常。
桌面堆
然后我们发现了真正的罪魁祸首:它是“桌面堆”。默认情况下,交互式用户会话的所有图形应用程序都在所谓的“桌面”中执行。分配给此类桌面的资源是有限的(但可配置)。
注意:用户对象消耗了桌面堆的大部分内存空间。这包括窗户。关于Desktop Heap的更多信息,可以参考NTDebugging MSDN博客上发表的非常好的文章:
真正的解决方案是什么?变绿!
增加桌面堆是一种有效的解决方案,但这不是最终的解决方案。真正的解决方案是消耗更少的资源(在我们的例子中是更少的窗口句柄)。我可以猜到你对这个解决方案有多失望。这真的是我能想到的全部吗?好吧,这里没有什么大秘密。唯一的出路就是瘦。拥有不太复杂的 UI 是一个好的开始。这对资源有好处,对可用性也有好处。下一步是避免浪费,保护资源并回收利用!
以下是我们如何在我的客户的应用程序中执行此操作:
我们使用 TabControls 并动态创建每个选项卡的内容,当它变得可见时;我们使用可展开/可折叠的区域,并且仅在需要时再次用控件和数据填充它们;我们尽快释放资源(使用 Dispose 方法)。当一个区域被折叠时,可以清除它的子控件。选项卡隐藏时也是如此;我们使用 MVP 设计模式,这有助于实现上述目标,因为它将数据与视图分开;我们使用布局引擎、标准的 FlowLayoutPanel 和 TableLayoutPanel 或自定义引擎,而不是创建嵌套面板、GroupBoxes 和拆分器的深层层次结构(一个空的拆分器本身会消耗三个窗口句柄......)。如果您需要构建丰富的 Windows 窗体屏幕,以上只是提示您可以做什么。那里' 毫无疑问,您可以找到其他方法。在我看来,您应该做的第一件事是围绕用例和场景构建应用程序。这有助于仅显示给定时间和给定用户所需的内容。
当然,另一种解决方案是使用不依赖句柄的系统...... WPF 有人吗?