在.NET WinForm TreeView中出现了内存不足异常

6
我有一个树形视图,基于树形视图中的项目,我在右侧有一个列表视图。所以几乎整个UI看起来像我们的Windows资源管理器。现在我面临的问题是,当我从右侧的列表视图中删除大量对象时,左侧的树形视图会部分绘制(可以说是小部分)。当我在VS IDE中附加CLR异常时,它指向了样本树.EndUpdate(); 这一行,并显示内存不足的异常。当我在列表视图中添加下一个项目时,一切都恢复正常,我的意思是树形视图完全被绘制。 我收到的异常信息是:
System.OutOfMemoryException occurred
  Message=Out of memory.
  Source=System.Drawing
  StackTrace:
       at System.Drawing.Graphics.FromHdcInternal(IntPtr hdc)
       at System.Drawing.Font.ToLogFont(Object logFont)
       at System.Drawing.Font.ToHfont()
       at System.Windows.Forms.Control.FontHandleWrapper..ctor(Font font)
       at System.Windows.Forms.OwnerDrawPropertyBag.get_FontHandle()
       at System.Windows.Forms.TreeView.CustomDraw(Message& m)
       at System.Windows.Forms.TreeView.WmNotify(Message& m)
       at System.Windows.Forms.TreeView.WndProc(Message& m)
       at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
       at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
       at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
       at System.Windows.Forms.UnsafeNativeMethods.SendMessage(HandleRef hWnd, Int32 msg, IntPtr wParam, IntPtr lParam)
       at System.Windows.Forms.Control.SendMessage(Int32 msg, IntPtr wparam, IntPtr lparam)
       at System.Windows.Forms.Control.ReflectMessageInternal(IntPtr hWnd, Message& m)
       at System.Windows.Forms.Control.WmNotify(Message& m)
       at System.Windows.Forms.Control.WndProc(Message& m)
       at System.Windows.Forms.ScrollableControl.WndProc(Message& m)
       at System.Windows.Forms.UserControl.WndProc(Message& m)
       at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
       at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
       at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
       at System.Windows.Forms.UnsafeNativeMethods.CallWindowProc(IntPtr wndProc, IntPtr hWnd, Int32 msg, IntPtr wParam, IntPtr lParam)
       at System.Windows.Forms.NativeWindow.DefWndProc(Message& m)
       at System.Windows.Forms.Control.DefWndProc(Message& m)
       at System.Windows.Forms.Control.WndProc(Message& m)
       at System.Windows.Forms.TreeView.WndProc(Message& m)
       at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
       at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
       at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
       at System.Windows.Forms.UnsafeNativeMethods.SendMessage(HandleRef hWnd, Int32 msg, Int32 wParam, Int32 lParam)
       at System.Windows.Forms.Control.EndUpdateInternal(Boolean invalidate)
       at System.Windows.Forms.TreeView.EndUpdate()

您有没有想过为什么我的树形视图只被画了一小部分,而连续的修改会完全重绘?下面是代码片段:

 if( ( values != null ) &&
       ( values .OverallState != ToBeDeleted ) &&
       ( values .OverallState != .Deleted ) )
    {
       TreeView tree = this.TreeView;
       if( tree != null )
       {
          tree.BeginUpdate();
       }
       TryUpdate();
       TryPopulate();
       if( tree != null )
       {
          tree.EndUpdate();  // here exception coming
       }
    }

更新 我正在这样使用字体

case State.Modified:
                     NodeFont = new Font(TreeView.Font, FontStyle.Bold);
break;

这会出现什么问题吗?


WinForm的TreeView有没有已知的问题,或者任何索引变成了-1? - vettori
这里也有同样的问题。任务管理器显示该进程已创建了10,000个GDI字体句柄(然后它崩溃了,10,000是最大值)。只有在同时创建很多节点时才会发生这种情况。 - dacap
3个回答

3
这种崩溃通常是由资源泄漏引起的。而忘记在任何 System.Drawing 类对象上调用 Dispose() 方法是非常常见的原因。通常垃圾回收器会替你处理,但特别是当你使用 ownerdraw 时,它可能不会运行得足够频繁以避免麻烦。当你使用了 10,000 个 GDI 对象后,Windows 就会关闭你的程序,这就是崩溃的结果。
你可以轻松地通过任务管理器来诊断此问题。查看+选择列,选择手柄、用户对象和 GDI 对象进行勾选。在使用过程中观察添加的这些列。不断增长的数字预示着 OOM 崩溃。
首先检查你的 DrawNode 事件处理程序,因为它很可能被频繁调用。但它也可能是由其他绘制代码引起的。确保使用 using 语句创建像 Graphics、Pen、Brush、Font 等绘图对象,这样它们在使用后就得到了保证被释放。从任务管理器获得的诊断结果会告诉你是否解决了问题。

你可能是正确的,我现在想知道的是,当删除了很多对象后出现问题(树形视图被截断一半),再次添加单个对象后,一切都会变得正常,这是什么原因? - vettori
在这个问题上纠结没有太大意义,显然更少的数据会导致你忘记释放的句柄更少。使用事实,任务管理器告诉了你什么? - Hans Passant
任务管理器显示句柄、用户对象和GDI对象的计数增加,但不是连续的,并且它在某个地方停止并开始减少到某些值。我修改了我的问题。在使用字体时,我没有使用using语句。 - vettori
3
问题似乎出现在.NET内部绘图方法中,即缺少Dispose()和using{}块。更改NodeFont时会出现问题,而不是自定义节点绘制代码(GDI对象增加到10,000)。此外,问题并不是因为创建多个Font()实例(你只需要创建一个),而是因为我们更改了多个节点的NodeFont。(TreeView .NET绘图代码在内部创建LOGFONT或HFONT对象,但没有立即处理掉它们。) - dacap
1
我认为这也是树视图中的一个错误。我已经打开了ProcessExplorer,它显示每次设置节点文本时GDI计数增加4k。当然,这不会持续很长时间。这是在完全加载树之后。最初一切都很好。尝试编辑节点文本,然后GDI计数飙升。似乎与树的大小有关。我将尝试使用Syncfusion的树。 - Adam Bruss
我也认为这是TreeView的一个bug。我通过减少执行“NodeFont =某个字体”的节点数量来解决它。现在我的代码会检查节点是否具有粗体字体,然后再设置字体,并通过将NodeFont设置为“null”来设置节点为非粗体。问题与创建太多字体对象无关 - 我只使用了两个(现在只使用一个,因为我意识到不需要为非粗体使用字体对象,只需使用“null”即可)。 - MarkJ

1
我刚遇到了完全相同的问题。它似乎只会在比XP更晚的Windows系统中出现,当我删除BeginUpdate()EndUpdate()调用时,它就不会发生。
因此,作为一种解决方法,我建议尝试删除BeginUpdate()EndUpdate()调用。这意味着在更新节点时可能会有一些视觉卡顿,但是好处是它不会崩溃。这肯定是一个净胜利。
我在MSDN / Connect上没有找到任何解决此问题的内容,而且我现在没有时间编写自包含的测试用例,但我确实认为这是后期Windows版本中TreeViews批量更新处理中的错误。

此外,如果您只是将 BeginUpdate() 替换为 myTreeView.Visible = false,并将 EndUpdate() 替换为 myTreeView.Visible = true - 您可以避免所有屏幕闪烁,并且仍然不会遇到错误。 (此外,这似乎具有相当不错的性能。) - Marcus Mangelsdorf

1
在更改节点字体或颜色后,您可以使用 System.GC.Collect() 强制执行垃圾回收。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接