.NET Framework 4.8下UserControl设置DoubleBuffered=true引发内存泄漏
问题描述
项目基于.NET Framework 4.8,我开发了一个设置DoubleBuffered = true的UserControl。每次调用窗体的Invalidate()方法使该控件失效时,PrivateMemorySize64会增加约1000字节。Paint事件处理程序已被注释,排除了绘制代码导致内存泄漏的可能。测试程序运行14小时后会因OutOfMemoryException崩溃,我原本预期PrivateMemorySize64会保持稳定。
代码示例
UserControl 代码
using System.Drawing; using System.Windows.Forms; public partial class BitmapRender : UserControl { private Bitmap TestBmp = new Bitmap(@"D:\test.bmp"); public BitmapRender() { InitializeComponent(); DoubleBuffered = true; // 该行导致PrivateMemorySize64持续增长 // this.Paint += new System.Windows.Forms.PaintEventHandler(this.This_Paint); } /* private void This_Paint(object sender, System.Windows.Forms.PaintEventArgs e) { e.Graphics.DrawImage(TestBmp, 0, 0, this.Width, this.Height); } */ }
主窗体代码
using System; using System.Windows.Forms; public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void timer1_Tick(object sender, EventArgs e) { // 每200毫秒执行一次 BitmapRender1.Invalidate();// 当UserControl设置DoubleBuffered=true时,此调用引发内存泄漏 } }
问题分析与解决
原因
.NET Framework 4.8中,启用默认DoubleBuffered的控件,即使没有实际绘制操作,每次调用Invalidate()后,内部的双缓冲机制依然会创建临时缓冲位图,但这些资源未被及时回收,导致内存持续累积。
解决办法
手动管理双缓冲,替代默认属性
放弃使用控件自带的DoubleBuffered属性,在需要绘制时手动创建和销毁缓冲位图,确保资源及时释放。修改后的UserControl代码如下:using System.Drawing; using System.Windows.Forms; public partial class BitmapRender : UserControl { private Bitmap TestBmp = new Bitmap(@"D:\test.bmp"); public BitmapRender() { InitializeComponent(); // 移除默认的DoubleBuffered设置 } protected override void OnPaint(PaintEventArgs e) { // 使用using语句自动释放缓冲资源 using (Bitmap bufferBmp = new Bitmap(this.Width, this.Height)) using (Graphics bufferGfx = Graphics.FromImage(bufferBmp)) { // 执行绘制逻辑(如果需要) bufferGfx.DrawImage(TestBmp, 0, 0, this.Width, this.Height); // 将缓冲内容绘制到控件 e.Graphics.DrawImage(bufferBmp, 0, 0); } } }避免无意义的Invalidate调用
如果控件没有实际界面更新需求,不要通过定时器频繁调用Invalidate()。仅当控件内容确实需要刷新时,再触发该方法。重写OnPaintBackground方法
重写控件的OnPaintBackground方法并留空,避免默认背景绘制操作创建额外缓冲资源:protected override void OnPaintBackground(PaintEventArgs e) { // 空实现,跳过默认背景绘制 }临时缓解方案(不推荐)
如果必须保留默认双缓冲,可在每次Invalidate()后触发垃圾回收,但这会影响程序性能,仅作为临时应急手段:private void timer1_Tick(object sender, EventArgs e) { BitmapRender1.Invalidate(); GC.Collect(); GC.WaitForPendingFinalizers(); }
内容的提问来源于stack exchange,提问作者Bob Aune
相关产品推荐
相关产品推荐

