Possible bug with .net winforms, or am I just miss

2020-07-28 11:05发布

问题:

Could someone please tell me what is going wrong?

After removing tons of code to find our GDI object leak (using task manager and watching the "GDI Objects" column grow to 10,000 and our app crashing) I reduced the code down to only .net code without any custom business code. We are still getting the issue.

I created a test app to replicate the issue, which has the following basic behavior.

  • Open the a form 150 times (150 is nothing special, just a number large enough to easily see "stuck" handles). A timer on the form will close the form after 1 second
  • Run the Garbage collector (not really necessary, but can help get rid of the "good" or "working" objects that are not part of the issue)
  • Observe manually the GDI Object count of the app (you should do this before and after you open the form 150 times.) Before I run the test I usually get a count of 36, after the test it is about 190. Ever time I run the test this count increases by about 150.

Now the form that is being launched 150 times is setup in a specific way (Lets call the form "BadForm". The is a static datatable the is bound to a combobox on the form.

BadForm has a comboBox on it, and a timer. Here is the code for the form:

using System;
using System.Data;
using System.Drawing;
using System.Windows.Forms;
namespace GDIObjectLeakTest
{
  public partial class MyForm :Form
  {
    public static DataTable CachedNodeType = new DataTable();

    public MyForm()
    {
      InitializeComponent();
      this.comboBox1.SelectedIndexChanged += new EventHandler(this.comboBox1_SelectedIndexChanged);
      this.Font = new Font("Modern No. 20", 8.249999F, FontStyle.Regular, GraphicsUnit.Point, ((byte)(0))); ;
      comboBox1.DataSource = CachedNodeType;
    }

    private void timer1_Tick(object sender, EventArgs e)
    {
      Close();
    }

    private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
    { }
  }
}   

Here is the code for the main form of the app that runs the test. It has 2 buttons on it. Button1 runs the BadForm 150 times. Button 2 runs the garbage collector 100 times (Once or twice isn't good enough for me I guess) (I'm using the Garbage Collector just to proove there is/isn't a problem).

private void button1_Click(object sender, EventArgs e)
{
  try
  {
    for(int i = 0; i < 150; i++)
    {
      //new SearchForm().Show();
      new MyForm().Show();
    }
  } catch(Exception ee)
  {
    throw;
  }
}

private void button2_Click(object sender, EventArgs e)
{
  for(int i = 0; i < 100; i++)
  {
    GC.Collect();
    GC.WaitForPendingFinalizers();
  }
}

回答1:

Try adding this to the top of your dispose method (in the designer file):

comboBox1.DataSource = null;


回答2:

I see two potential issues here:

  1. If you create an instance of an object that implements IDisposable, you must call the Dispose method on it, or wrap its use in a using block. Form implements IDisposable, so your code should look like the following:

        using (Form myform = new Form())
        {
            myform.Show();
        }   //frees resource by calling Dispose automatically
    

    otherwise, you will see the memory leaks you see here, because you are creating new instances of your Form, but then you are never freeing its resources. Garbage collection might eventually free up Windows resources for you in WinForms, due to the way that finalizers are written in the BCL, but calling Dispose will do it immediately.

  2. You are creating a new Font object and assigning it to the Font property each time your form is initialized. Not that this is necessarily bad (designer generated code does this copiously), but each new Font instance occupies a GDI handle, which will not be freed automatically unless you call Font.Dispose. My guess is that you are leaving another Font object behind that might not be getting Disposed properly. You may want to optimize this in some way (such as by sharing a Font instance) if you are making huge numbers of these objects. In at least one case, not calling Dispose on fonts will cause this memory leak.



回答3:

You are using Font object which is an GDI object and not disposed until you dispose it. Either use Font object with using statement, or call Font.Dispose on FormClose event.