我们正在开发一个非常大的.NET WinForms 复合应用程序 - 不是 CAB,而是一个类似的自制框架。 我们在 Windows Server 2003 上运行的 Citrix 和 RDP 环境中运行。
我们开始遇到随机且难以重现的“创建窗口句柄错误”错误,该错误似乎是我们应用程序中的旧式句柄泄漏。 我们大量使用第 3 方控件(Janus GridEX、Infralution VirtualTree 和 .NET Magic 对接),并且根据数据库中的元数据进行大量内容动态加载和渲染。
Google 上有很多关于此错误的信息,但没有很多关于如何避免该领域问题的可靠指导。
stackoverflow 社区对我构建句柄友好的 winforms 应用程序有什么好的指导吗?
We're working on a very large .NET WinForms composite application - not CAB, but a similar home grown framework. We're running in a Citrix and RDP environment running on Windows Server 2003.
We're starting to run into random and difficult to reproduct "Error creating window handle" error that seems to be an old fashion handle leak in our application. We're making heavy use of 3rd Party controls (Janus GridEX, Infralution VirtualTree, and .NET Magic docking) and we do a lot of dynamic loading and rendering of content based on metadata in our database.
There's a lot of info on Google about this error, but not a lot of solid guidance about how to avoid issues in this area.
Does the stackoverflow community have any good guidance for me for building handle-friendly winforms apps?
发布评论
评论(11)
问候,
由于面板中添加了大量用于图像处理的 PictureBox,我面临同样的问题。 原始代码使用 Controls.RemoveAt(0) 仍然会导致问题。 只是更改为 Controls[0].Dispose -> 似乎问题就消失了。
Greeting,
I facing same problem due to large amount of PictureBox for image processing was added to panel. Original code use Controls.RemoveAt(0) still cause the problem. Just changed to Controls[0].Dispose -> seem like problem away.
了解此错误
突破 Windows 的极限:USER 和 GDI 对象 – 第 1 部分,作者:Mark Russinovich:
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”>“Handles”。 选择列:
执行导致问题的步骤,并观察 USER 对象计数增加到大约 10,000 个或 GDI 对象或句柄达到其限制。
当您看到对象或句柄增加(通常急剧增加)时,您可以通过单击“暂停”按钮来停止 Visual Studio 中的代码执行。
然后,只需按住 F10 或 F11 即可浏览代码,观察对象/句柄计数急剧增加的情况。
到目前为止我发现的最好的工具是 NirSoft 的 GDIView,它分解了 GDI 句柄字段:
我追踪到设置 DataGridViews“过滤器组合框”列位置和宽度时使用的代码
:我的案例(上面)的解决方案是显式处置并清理,解决了释放 USER 对象的问题。
这是堆栈跟踪:
这是 Fabrice 的一篇有用的文章帮助我解决了限制:
“创建窗口句柄时出错”
当我为客户端开发的大型 Windows 窗体应用程序被频繁使用时,用户经常会收到“创建窗口句柄时出错”的异常。
除了应用程序消耗太多资源这一事实(这是我们已经在解决的一个单独的问题)之外,我们还很难确定哪些资源正在耗尽以及这些资源的限制是什么。
我们首先考虑关注 Windows 任务管理器中的句柄计数器。 这是因为我们注意到某些进程往往会消耗比正常情况更多的资源。 然而,这个计数器并不是一个好的计数器,因为它跟踪文件、套接字、进程和线程等资源。 这些资源被称为内核对象。
我们应该关注的其他类型的资源是 GDI 对象和用户对象。 您可以在 MSDN 上获取三类资源的概述。
用户对象
窗口创建问题与用户对象直接相关。
我们试图确定应用程序可以使用的用户对象的限制。
每个进程的配额为 10,000 个用户句柄。 这个值可以在注册表中更改,但是在我们的例子中,这个限制并不是真正的阻碍因素。
另一个限制是每个 Windows 会话 66,536 个用户句柄。 该限制是理论上的。 在实践中,您会发现它无法到达。 在我们的例子中,在当前会话中的用户对象总数达到 11,000 之前,我们遇到了可怕的“创建窗口句柄时出错”异常。
桌面堆
然后我们发现哪个限制才是真正的罪魁祸首:它是“桌面堆”。
默认情况下,交互式用户会话的所有图形应用程序都在所谓的“桌面”中执行。 分配给此类桌面的资源是有限的(但可配置)。
注意:用户对象消耗了桌面堆的大部分内存空间。 这包括窗户。
有关桌面堆的更多信息,可以参考 NTDebugging MSDN 博客上发表的非常好的文章:
真正的解决方案是什么? 绿色环保!
增加桌面堆是一种有效的解决方案,但这不是最终的解决方案。 真正的解决方案是消耗更少的资源(在我们的例子中更少的窗口句柄)。 我可以猜测您对这个解决方案有多失望。 这真的是我能想到的全部吗?
嗯,这里没有什么大秘密。 唯一的出路就是变瘦。 拥有不太复杂的用户界面是一个好的开始。 这有利于资源,也有利于可用性。 下一步是避免浪费、保护资源并回收利用!
以下是我们在客户的应用程序中执行此操作的方式:
我们使用 TabControls,并在每个选项卡变得可见时动态创建它的内容;
我们使用可扩展/可折叠区域,并仅在需要时才用控件和数据填充它们;
我们尽快释放资源(使用Dispose方法)。 当一个区域折叠时,可以清除它的子控件。 选项卡隐藏时也是如此;
我们使用 MVP 设计模式,这有助于使上述成为可能,因为它将数据与视图分开;
我们使用布局引擎、标准 FlowLayoutPanel 和 TableLayoutPanel 或自定义引擎,而不是创建嵌套面板、GroupBox 和拆分器的深层层次结构(空拆分器本身消耗三个窗口句柄...)。
以上只是关于如果您需要构建丰富的 Windows 窗体屏幕可以执行哪些操作的提示。 毫无疑问,您可以找到其他方法。
我认为您应该做的第一件事是围绕用例和场景构建应用程序。 这有助于仅显示给定时间和给定用户所需的内容。
当然,另一种解决方案是使用不依赖句柄的系统... WPF 有人吗?
Understanding this error
Pushing the Limits of Windows: USER and GDI Objects – Part 1 by Mark Russinovich:
https://blogs.technet.microsoft.com/markrussinovich/2010/02/24/pushing-the-limits-of-windows-user-and-gdi-objects-part-1/
Troubleshooting this error
You need to be able to reproduce the problem. Here is one way of recording the steps to do that https://stackoverflow.com/a/30525957/495455.
The easiest way to work out what is creating so many handles is to have TaskMgr.exe open. In TaskMgr.exe you need to have the USER Object, GDI Object and Handles columns visible as shown, to do this choose View Menu > Select Columns:
Go through the steps to cause the problem and watch the USER Object count increase to around 10,000 or GDI Objects or Handles reach their limits.
When you see the Object or Handles increase (typically dramatically) you can halt the code execution in Visual Studio by clicking the Pause button.
Then just hold down F10 or F11 to cruise through the code watching when the Object/Handle counts increases dramatically.
The best tool I have found so far is GDIView from NirSoft, it breaks up the GDI Handle fields:
I tracked it down to this code used when setting DataGridViews "Filter Combobox" Columns Location and Width:
In my case (above) the solution was explicitly disposing and cleaning up that fixed the issue of releasing USER objects.
This is the stack trace:
Here is the crux of a helpful article by Fabrice that helped me work out the limits:
"Error creating window handle"
When a big Windows Forms application I'm working on for a client is used actively, users often get "Error creating window handle" exceptions.
Aside from the fact that the application consumes too much resources, which is a separate issue altogether that we are already addressing, we had difficulties with determining what resources were getting exhausted as well as what the limits are for these resources.
We first thought about keeping an eye on the Handles counter in the Windows Task Manager. That was because we noticed that some processes tended to consume more of these resources than they normally should. However, this counter is not the good one because it keeps track of resources such as files, sockets, processes and threads. These resources are named Kernel Objects.
The other kinds of resources that we should keep an eye on are the GDI Objects and the User Objects. You can get an overview of the three categories of resources on MSDN.
User Objects
Window creation issues are directly related to User Objects.
We tried to determine what the limit is in terms of User Objects an application can use.
There is a quota of 10,000 user handles per process. This value can be changed in the registry, however this limit was not the real show-stopper in our case.
The other limit is 66,536 user handles per Windows session. This limit is theoretical. In practice, you'll notice that it can't be reached. In our case, we were getting the dreaded "Error creating window handle" exception before the total number of User Objects in the current session reached 11,000.
Desktop Heap
We then discovered which limit was the real culprit: it was the "Desktop Heap".
By default, all the graphical applications of an interactive user session execute in what is named a "desktop". The resources allocated to such a desktop are limited (but configurable).
Note: User Objects are what consumes most of the Desktop Heap's memory space. This includes windows.
For more information about the Desktop Heap, you can refer to the very good articles published on the NTDebugging MSDN blog:
What's the real solution? Be green!
Increasing the Desktop Heap is an effective solution, but that's not the ultimate one. The real solution is to consume less resources (less window handles in our case). I can guess how disappointed you can be with this solution. Is this really all what I can come up with??
Well, there is no big secret here. The only way out is to be lean. Having less complicated UIs is a good start. It's good for resources, it's good for usability too. The next step is to avoid waste, to preserve resources, and to recycle them!
Here is how we're doing this in my client's application:
We use TabControls and we create the content of each tab on the fly, when it becomes visible;
We use expandable/collapsible regions, and again fill them with controls and data only when needed;
We release resources as soon as possible (using the Dispose method). When a region is collapsed, it's possible to clear it's child controls. The same for a tab when it becomes hidden;
We use the MVP design pattern, which helps in making the above possible because it separates data from views;
We use layout engines, the standard FlowLayoutPanel and TableLayoutPanel ones, or custom ones, instead of creating deep hierarchies of nested panels, GroupBoxes and Splitters (an empty splitter itself consumes three window handles...).
The above are just hints at what you can do if you need to build rich Windows Forms screens. There's not doubt that you can find other approaches.
The first thing you should do in my opinion is building your applications around use cases and scenarios. This helps in displaying only what's needed at a given time, and for a given user.
Of course, another solution would be to use a system that doesn't rely on handles... WPF anyone?
当我子类化 NativeWindow 并手动调用 CreateHandler 时,出现此错误。 问题是我忘记在我的 WndProc 重写版本中添加 base.WndProc(m) 。 它导致了同样的错误
I had this error when I subclassed NativeWindow and called CreateHandler manually. The problem was I forgot to add base.WndProc(m) in my overriden version of WndProc. It caused the same error
我遇到了这个异常,因为无限循环创建新的 UI 控件并设置其属性。
循环多次后,当更改控件可见属性时抛出此异常。
我发现用户对象和 GDI 对象(来自任务管理器)都相当大。
我猜你的问题与那些 UI 控件耗尽系统资源的原因类似。
I met this exception because endless loop creating new UI control and set its properties.
After looped many times, this excption was thrown when change control visible property.
I found both User Object and GDI Object (From Task Manager) are quite large.
I guess your issue is similar reason that system resources are exhaust by those UI controls.
我在工作中使用 Janus Controls。 就处理自己而言,它们非常麻烦。 我建议您确保正确处置它们。 此外,与它们的绑定有时不会释放,因此您必须手动解除对象的绑定才能处理控件。
I am using the Janus Controls at work. They are extremely buggy as far as disposing of themselves go. I would recommend that you make sure that they are getting disposed of correctly. Also, the binding with them sometimes doesn't release, so you have to manually unbind the object to dispose of the control.
我在向面板添加控件时遇到了此异常,因为面板中的子控件未清除。 如果将子控件放置在面板中,则错误已修复。
I faced this exception while adding controls in to the panel, Because in panel child controls not cleared. If dispose the child controls in panel then bug fixed.
我遇到了相同的 .Net 运行时错误,但我的解决方案不同。
我的场景:
在返回 DialogResult 的弹出对话框中,用户可以单击按钮来发送电子邮件。 我添加了一个线程,以便在后台生成报告时 UI 不会锁定。 这种情况最终得到了不寻常的错误消息。
导致问题的代码:
此代码的问题在于,线程立即启动并返回,这会导致返回 DialogResult,从而在线程可以正确从字段中获取值之前处理对话框。
此场景的修复:
解决方法是在将值传递到创建线程的方法之前获取并存储这些值。
I ran into the same .Net runtime error but my solution was different.
My Scenario:
From a popup Dialog that returned a DialogResult, the user would click a button to send an email message. I added a thread so the UI didn't lock up while generating the report in the background. This scenario ended up getting that unusual error message.
The code that resulted in problem:
The problem with this code is that the thread immediately starts and returns which results in the DialogResult being returned which disposes the dialog before the thread can properly grab the values from the fields.
The fix for this scenario:
The fix is to grab and store the values before passing them into the method which creates the thread.
当我开始在 WinForm 应用程序中使用线程时,发生了同样的错误,
我使用堆栈跟踪来查找引发错误的原因,并发现 infragistics 的 UltraDesktopAlert 组件位于此后面,因此我以不同的方式调用它,并且错误现在消失了。
完整的代码如下所示。
我也无法使用 GDI 实用程序来查找我的应用程序创建了多少个句柄,但我的应用程序(64 位)在其列表中不可用。
另一个解决方案是在以下位置 HKEY 将桌面堆值更改为
SharedSection=1024,20480,768
,但我的已经具有相同的值。 只有调用方法委托对我有用。 希望这有帮助。
same error occured when i started using threading in my WinForm App,
i used stack trace to find what is throwing error and found out UltraDesktopAlert component of infragistics was behind this so i invoked it differently and error is now gone.
the full code will look like this.
also i was unable to use GDI utility to find how many handle my app creates but my app (64bit) was not available in its list.
another solution was to change desktop heap value to
SharedSection=1024,20480,768
at following location HKEYbut mine was already with same values. only invoke method delegate worked for me. hope this helped.
就我而言,我重写了
WndProc(ref Message m)
但没有调用base.WndProc(ref m)
;In my case I was overriding
WndProc(ref Message m)
but not callingbase.WndProc(ref m)
;我发现了许多 UI 未按 WinForms 预期卸载的问题。
以下是一些一般提示:
I have tracked down a lot of issues with UIs not unloading as expected in WinForms.
Here are some general hints:
我有同样的错误,但在我的 C# Windows 服务中。 在我的服务中,在 OnStart() 方法中,我使用“using”语句创建一个新的 WinForms 对象。 就在那之后我的服务抛出了这个错误。
我的修复很简单,事实证明,如果我使用“本地系统”登录启动我的服务,则会抛出该错误,但如果我使用帐户类型启动我的服务,那就一切都很好。
我希望有人会觉得这很有用。
I had the same error but in my C# Windows Service. Inside my service, in OnStart() method I was creating a new WinForms Object using a "using" statement. Just after that my service was throwing this error.
My fix was simple, it turned out that if I start my service using a Log On As "Local System" it was throwing that error, but If I start my service using a Account Type it was all good.
I hope someone will find this useful.