一个vb.net应用程序遇到了OutOfMemoryException异常。

10
在我的VB.Net应用程序中运行时,我遇到了错误。这个错误并不总是出现,所以我也无法重现它。也没有确切的顺序可以重现这个错误。
错误信息如下:
System.OutOfMemoryException: 内存不足。 at System.Drawing.Graphics.FromHdcInternal(IntPtr hdc) at System.Windows.Forms.ToolStrip.OnPaint(PaintEventArgs e) at System.Windows.Forms.Control.PaintWithErrorHandling(PaintEventArgs e, Int16 layer, Boolean disposeEventArgs) at System.Windows.Forms.Control.WmPaint(Message& m) at System.Windows.Forms.Control.WndProc(Message& m) at System.Windows.Forms.ScrollableControl.WndProc(Message& m) at System.Windows.Forms.ToolStrip.WndProc(Message& m) at System.Windows.Forms.StatusStrip.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.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
错误描述:
MyApplication_UnhandledException
在这个错误后,我收到一条消息,上面写着:"内存不足,无法创建位图。关闭一个或多个应用程序以增加可用内存。"
当我检查应用程序的内存使用情况时,并没有那么高。这个错误不会反复出现。那我该如何解决这个问题?如何进行故障排除?我尝试使用.NET和Redgate内存分析器来运行我的应用程序。
下面是一个未管理内存使用量的屏幕截图。我不确定这些值是否很高。
更新:

我再次遇到了错误。检查了GDI对象,发现有9998个。因此,错误是由于GDI对象过多引起的。现在问题是如何解决。然后我使用了GDIView工具进行了检查。通过该工具,我得到了pen-2954 brush-5918 font-90 bitmap-13等等总共9998个GDI。那么这些pen和brush是什么呢?在我的代码中,我没有使用brush或pen(我搜索了代码中的“pen”和“brush”,但没有找到)。所以请帮助我解决这个问题。


你将什么赋值给Tooltip? - Tigran
1
请问您能展示一下您的代码吗?最好是与位图相关的部分。 - Ry-
1
唯一显而易见的是你相当频繁地使用ActiveX控件,这可能会阻止垃圾收集器经常运行以保持正常。当你不在System.Drawing对象上使用Dispose()方法时,所面临的问题就是这种麻烦。当你使用太多句柄时,它会像这样中断。 - Hans Passant
2个回答

11
在您的任务管理器中,转到“查看”菜单以选择要在“进程”选项卡中显示哪些列。选择您想要显示的GDI对象列。我非常确定您会看到您的进程的总GDI对象已达到10000,这是任何进程的最大值。
这不是物理内存使用量的问题。从这个意义上说,错误消息非常糟糕和误导人。问题在于您已经用完了GDI句柄。Windows下的每个进程都被限制为可以创建的最大GDI句柄数量。当前限制为每个进程10000个句柄。
我假设您的问题是GDI句柄,因为在绘制控件的过程中尝试创建新位图时抛出异常。位图是GDI对象。创建位图会使用一个GDI句柄。因此,那很可能是原因。
由于错误发生在标准ToolStrip控件中,它本身不太可能是ToolStrip的错误。更有可能的是,在程序的其他地方,您正在使用所有GDI句柄,然后当控件尝试绘制自己时,失败了,因为没有剩余句柄。
每当您创建GDI对象(如笔和位图)时,都需要确保处理这些对象。获取GDI句柄的所有GDI类都实现了IDisposable接口。当对象被处理时,它们会自动删除其句柄。但是,如果您从不处理对象,则句柄永远不会被删除,您的GDI对象计数将继续增长。
要处理任何IDisposable对象,只需在完成对象后调用Dispose方法,例如:
Dim b As New Bitmap("test.bmp")
'...
b.Dispose()

然而,如果可以的话,最好使用Using块声明IDisposable对象的变量,像这样:

Using b As New Bitmap("test.bmp")
    '...
End Using

使用Using块,Dispose方法将自动调用,因此您无需显式调用它。之所以Using块比自己调用Dispose更好,是因为如果在Using块内部抛出异常,则Dispose方法仍将自动调用。如果您自己明确调用它而没有Using块,则更容易错过每个需要调用它的地方。
要在代码中找到问题区域,请在调试器中运行程序并逐步执行代码。在您逐步执行代码时,保持打开任务管理器,显示GDI对象列。观察任务管理器中的GDI对象列,您将看到随着创建新的GDI对象,计数会增加。使用此方法,应该很容易看到问题所在。

1
在我的应用程序中,当点击网格时,Msflexgrid没有被处理。每次点击网格都会增加gdi对象,但我找到了解决方案。通过在调试器中运行程序,我解决了这个问题。现在问题已经解决,gdi计数保持在250到300之间。非常感谢。 - IT researcher

0

我不是说这是唯一的答案,但它对我有用 - 最终!我有带有不同大小的jpg和其他图片的剪贴画文件夹。我正在使用datagridview组合vb.net应用程序来显示图片库。对于某些文件夹,它运行良好,而对于其他文件夹,则会出现“内存不足”的错误。我寻找了很长时间,然后检查了文件夹中实际存在的内容。我有一个额外的INDEX.DAT文件,一旦编写代码忽略此文件 - 问题就消失了!


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