且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

Windows窗体内存泄漏

更新时间:2023-12-06 09:49:10

根据我的经验,在Windows Forms中有一些情况,当处理的控件可以缓存在 LayoutEventArgs 对象中,并且它看起来像WinForms中的一些小错误。



某些详细信息:

System.Windows.Forms.Control 类型的每个实例包含 LayoutEventArgs 类型的私有成员变量 - cachedLayoutEventArgs 。而且, LayoutEventArgs 通常包含对某些特定控件的引用。您可以通过反射器清楚地看到所有这些事实。而且,有时候,由于某些原因,子控件处理不会影响父控件的布局过程,因此 cachedLayoutEventArgs 字段不会被清除。您可以使用mdi父表单通过暂停MdiClient的控件布局来关闭其子项来模仿这种情况:

  public partial class MdiParentForm:表单{
public MdiParentForm(){
InitializeComponent(); // this.IsMdiContainer = true
}
void buttonAddMdiChild_Click(object sender,EventArgs e){
MdiChildForm f = new MdiChildForm();
f.MdiParent = this;
f.Show();
}
void buttonCloseMdiChild_Click(object sender,EventArgs e){
MdiClient client = GetMdiClient(this);
customer.SuspendLayout();

if(ActiveMdiChild!= null)
ActiveMdiChild.Close();

client.ResumeLayout(false);
// !!!此时,MdiClient.cachedLayoutEventArgs包含对处理的控件(泄漏)的引用
}
static MdiClient GetMdiClient(Form frm){
if(frm!= null){
foreach (控制ctrl在frm.Controls){
如果(ctrl是MdiClient)
返回(MdiClient)ctrl;
}
}
返回null;
}
}
class MdiChildForm:Form {}

是一个简单的解决方法 - 通过触发 PerformLayout 方法,您可以有效地刷新缓存实例:

  class MdiChildForm:Form {
MdiClient parent;
protected override void OnParentChanged(EventArgs e){
base.OnParentChanged(e);
var mdiClient = Parent作为MdiClient;
if(mdiClient!= parent){
if(parent!= null)
parent.PerformLayout();
parent = mdiClient;
}
}
}

无论如何,我建议您在这方面联系 DevExpress支持,以确保内存泄漏你描述的与他们的控制无关,并得到最终解决方案。


I see a slight memory leak in my windows application. I use DevExpress XtraForm in my application. What I see is one instance of the form is always being kept in memory. If you open the same form multiple times it still keeps the reference of last form opened.

Ex. if you open 10 different form in the application and close all of them it would still not release the memory assigned to it because of some weird "MdiClient object references LayoutEventArgs object". Fortunately it keep reference of single item per type.

Here is the link to the Redgate memory profiler output.

https://dl.dropboxusercontent.com/u/2781659/Memory%20Leak.pdf

In the chart above the DepartmentsForm is diposed but cannot be GCed because of affectedComponent member of LayoutEventArgs referencing it.

Please advise if you see any obvious error.

From my experience there are some situation in Windows Forms when disposed controls can be cached within the LayoutEventArgs object and it looks like some kind of minor bug in WinForms.

Some details:
Each instance of the System.Windows.Forms.Control type contains a private member variable of the LayoutEventArgstype - cachedLayoutEventArgs . And, the LayoutEventArgs typically contains a reference to some specific control. You can clearly see all of these facts via Reflector. And, sometimes, the cachedLayoutEventArgs field is not cleared when the child control disposing does not affect the layout process of the parent control sue to some reasons. You can imitate this situation using the mdi parent form by suspending the MdiClient's control layout while closing its children:

public partial class MdiParentForm : Form {
    public MdiParentForm () {
        InitializeComponent(); //  this.IsMdiContainer = true
    }
    void buttonAddMdiChild_Click(object sender, EventArgs e) {
        MdiChildForm f = new MdiChildForm();
        f.MdiParent = this;
        f.Show();
    }
    void buttonCloseMdiChild_Click(object sender, EventArgs e) {
        MdiClient client = GetMdiClient(this);
        client.SuspendLayout();

        if(ActiveMdiChild != null)
            ActiveMdiChild.Close();

        client.ResumeLayout(false); 
        // !!! At this point the MdiClient.cachedLayoutEventArgs contains the reference to disposed control (leak)
    }
    static MdiClient GetMdiClient(Form frm) {
        if(frm != null) {
            foreach(Control ctrl in frm.Controls) {
                if(ctrl is MdiClient)
                    return (MdiClient)ctrl;
            }
        }
        return null;
    }
}
class MdiChildForm : Form { }

There is a simple workaround - by triggering the PerformLayout method, you can effectively flush-out that "cached" instance:

class MdiChildForm : Form {
    MdiClient parent;
    protected override void OnParentChanged(EventArgs e) {
        base.OnParentChanged(e);
        var mdiClient = Parent as MdiClient;
        if(mdiClient != parent) {
            if(parent != null)
                parent.PerformLayout();
            parent = mdiClient;
        }
    }
}

P.S. In any way I suggest you contact the DevExpress support in this regard, to sure that the memory leak you described is not related to their controls and get the final solution.