.NET Winforms C# 应用程序由于垃圾收集影响非托管库而崩溃

问题描述 投票:0回答:1

我有一个 .net C# 应用程序,它使用一些 Chart 和 PictureBox 控件,这些控件(尽管很少)会自发关闭。我的全局异常处理程序没有被触发 - 应用程序就消失了。

以下内容来自应用程序事件日志(三个单独的事件):

Application: MyApp.exe
Framework Version: v4.0.30319
Description: The process was terminated due to an unhandled exception.
Exception Info: System.AccessViolationException
   at System.Drawing.SafeNativeMethods+Gdip.GdipDrawImageRectI(System.Runtime.InteropServices.HandleRef, System.Runtime.InteropServices.HandleRef, Int32, Int32, Int32, Int32)
   at System.Drawing.Graphics.DrawImage(System.Drawing.Image, Int32, Int32, Int32, Int32)
   at System.Drawing.Graphics.DrawImage(System.Drawing.Image, System.Drawing.Rectangle)
   at System.Windows.Forms.PictureBox.OnPaint(System.Windows.Forms.PaintEventArgs)
   at System.Windows.Forms.Control.PaintWithErrorHandling(System.Windows.Forms.PaintEventArgs, Int16)
   at System.Windows.Forms.Control.WmPaint(System.Windows.Forms.Message ByRef)
   at System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef)
   at System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)

Faulting application name: MyApp.exe, version: 14.9.8764.31123, time stamp: 0x659050b3
Faulting module name: gdiplus.dll, version: 10.0.19041.3636, time stamp: 0xb8405853
Exception code: 0xc0000005
Fault offset: 0x0000000000024f80
Faulting process id: 0x2cd4
Faulting application start time: 0x01da3d633ffe83fc
Faulting application path: C:\MyApp\MyApp.exe
Faulting module path: C:\WINDOWS\WinSxS\amd64_microsoft.windows.gdiplus_6595b64144ccf1df_1.1.19041.3636_none_91a19322cc8a92a3\gdiplus.dll
Report Id: b44802f6-cb81-4188-89a2-6236e1ff5a37
Faulting package full name: 
Faulting package-relative application ID: 

Faulting application name: MyApp.exe, version: 14.9.8764.31123, time stamp: 0x659050b3
Faulting module name: gdiplus.dll, version: 10.0.19041.3636, time stamp: 0xb8405853
Exception code: 0xc000041d
Fault offset: 0x0000000000024f80
Faulting process id: 0x2cd4
Faulting application start time: 0x01da3d633ffe83fc
Faulting application path: C:\MyApp\MyApp.exe
Faulting module path: C:\WINDOWS\WinSxS\amd64_microsoft.windows.gdiplus_6595b64144ccf1df_1.1.19041.3636_none_91a19322cc8a92a3\gdiplus.dll
Report Id: c5b44473-b9e4-4555-a8cf-42f32959cbb5
Faulting package full name: 
Faulting package-relative application ID: 

它似乎在我的代码之外出现了错误。当试图追踪问题时,我遇到了这个:

https://learn.microsoft.com/en-gb/windows/win32/api/gdiplusinit/nf-gdiplusinit-gdiplusstartup?redirectedfrom=MSDN

...但在我看来,如果您从 dll 中运行 GDI+ 引擎(我不是 - 这是一个 exe),那么这似乎是在谈论初始化 GDI+ 引擎。另外,它很少崩溃(并且所有图表等都工作正常),但我想如果我未能正确初始化 GDI+,它根本无法工作。

我是否应该在 Form_Load 中初始化 GDI+? ...或者应用程序抛出异常并退出还有其他原因吗?

感谢您的帮助。

编辑:包含一些进一步的信息。我在 Nuget 上使用 Brian Cullen 的 WordCloud 库,其名称如下:

var wc = new WordCloudGen(new_width, new_height);

Image wordcloud_generated_image = wc.Draw(database_wordcloud_words, database_wordcloud_frequencies);

database_results_table.Invoke((MethodInvoker)delegate
{
      wordcloud_image.Image = wordcloud_generated_image;
...

它是多线程的(即词云是在后台生成的,我不希望它干扰用户在表单上其他地方进行交互的能力)。我认为就目前的目的而言,可以回避细节。如果为了论证,我们假设 WordCloud 库在某些情况下可以返回格式错误的图像,那么我如何才能阻止 GDI+ 中的错误。整个代码块都包含在 try/catch 中(并且还有一个全局异常处理程序),但它并没有捕获这个。

编辑2:

实现了自定义 PictureBox 类并启用损坏状态异常处理后,我能够捕获错误:

UnhandledExceptionEventArgs
Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
   at System.Drawing.SafeNativeMethods.Gdip.GdipDrawImageRectI(HandleRef graphics, HandleRef image, Int32 x, Int32 y, Int32 width, Int32 height)
   at System.Drawing.Graphics.DrawImage(Image image, Int32 x, Int32 y, Int32 width, Int32 height)
   at System.Drawing.Graphics.DrawImage(Image image, Rectangle rect)
   at System.Windows.Forms.PictureBox.OnPaint(PaintEventArgs pe)
   at Timesheet_Writer.ErrorManagedPictureBox.OnPaint(PaintEventArgs e) in D:\Develop\Visual C# 2017\MyApp.cs:line 39
   at System.Windows.Forms.Control.PaintWithErrorHandling(PaintEventArgs e, Int16 layer)
   at System.Windows.Forms.Control.WmPaint(Message& m)
   at System.Windows.Forms.Control.WndProc(Message& m)
   at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

它似乎是在应用程序最小化并离开一段时间后执行的,然后用户恢复窗口。我确实有一种感觉,这与图像被垃圾收集有关,尽管它实际上仍在使用中。

我饶有兴趣地读了这篇文章:https://www.codeproject.com/Tips/246372/Premature-NET-garbage-collection-or-Dude-wheres-my

我将看看是否可以通过 KeepAlive 显式管理垃圾收集。

c# .net gdi+
1个回答
0
投票

我找到了这个问题的答案。

我不认为垃圾回收实际上是在销毁对象;相反,它可以在内存中移动超过 85KB 的对象来优化它们(例如,参见 https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/large-object-heap)。如果这种情况发生在托管代码和非托管代码之间,则非托管代码不知道它已移动,而是使用旧指针,并尝试从实际上不再与 PictureBox 中的图像关联的内存空间中读取 - 因此出现访问冲突。

我创建了自己的 PictureBox 类,并添加了代码以将 Image 对象“固定”在垃圾收集器中,这样它就不会被移动。自从我这样做以来,我就没有遇到过例外情况。

using System; using System.Drawing; using System.Runtime.InteropServices; namespace MyApp { public partial class MemoryManagedPictureBox : System.Windows.Forms.PictureBox { public volatile Boolean exception_has_occurred = false; GCHandle memory_lock; ~MemoryManagedPictureBox() { if (this.Image != null) { this.Image.Dispose(); memory_lock.Free(); } } public void set_image(ref Image im) { if ( this.Image != null ) { this.Image.Dispose(); memory_lock.Free(); } this.Image = im; memory_lock = GCHandle.Alloc(this.Image, GCHandleType.Normal); } public void clear_image() { if (this.Image != null) { this.Image.Dispose(); this.Image = null; memory_lock.Free(); } } protected override void OnPaint(System.Windows.Forms.PaintEventArgs e) { try { base.OnPaint(e); exception_has_occurred = false; } catch { exception_has_occurred = true; } } } }
    
© www.soinside.com 2019 - 2024. All rights reserved.