Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Windows Form Memory leak

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.

like image 278
Paresh Varde Avatar asked Jan 10 '23 01:01

Paresh Varde


1 Answers

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.

like image 172
DmitryG Avatar answered Jan 12 '23 15:01

DmitryG