Почему последняя дочерняя форма MDI, которая была закрыта, не получает мусор? - PullRequest
3 голосов
/ 06 октября 2009

У нас были проблемы с утечками памяти в нашем приложении. Мне удалось воспроизвести одну из проблем на следующем простом примере:

Настройка репликации

1) Создайте следующий вспомогательный класс, который будет использоваться для отслеживания создания / уничтожения объекта.

public class TestObject
{
    public static int Count { get; set; }

    public TestObject()
    {
        Count++;
    }

    ~TestObject()
    {
        Count--;
    }
}

2) Создайте форму MDI с тремя кнопками, первая кнопка создаст новый дочерний элемент MDI следующим образом:

    private void ctlOpenMDI_Click(object sender, EventArgs e)
    {
        Form newForm = new Form();
        newForm.MdiParent = this;
        newForm.Tag = new TestObject();
        newForm.Show();
    }

Вторая кнопка будет использоваться для того же, но с дочерней формой не-MDI:

    private void ctlOpenNonMDIForm_Click(object sender, EventArgs e)
    {
        Form newForm = new Form();
        newForm.Tag = new TestObject();
        newForm.Show();
    }

Третья кнопка будет использоваться для сбора мусора, а затем для отображения количества живых экземпляров TestObject:

    private void ctlCount_Click(object sender, EventArgs e)
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();

        MessageBox.Show("Count: " + TestObject.Count);
    }

Шаги репликации

1) Нажмите кнопку Открыть форму MDI, затем закройте форму MDI, затем нажмите кнопку счета. Он вернет Count: 1. Дочерняя форма MDI и объект, на который она ссылается, не были собраны сборщиком мусора - что-то должно иметь ссылку на нее.

Также:

Нажмите три раза на открытую форму MDI, закройте все 3 формы, затем нажмите кнопку подсчета. Он вернет счетчик: 1. Кажется, что последняя закрытая дочерняя форма MDI не является сборщиком мусора.

Counter-случаи:

1) Нажмите кнопку Открыть форму без MDI, закройте ее. Затем нажмите кнопку подсчета. Он вернет Count: 0, форма и объект были собраны мусором.

Обход

Я могу обойти эту проблему, выполнив следующее:

        Form form = new Form();
        form.MdiParent = this;
        form.Show();
        form.Close();

Перед сборкой мусора. Это делает эту фиктивную форму последней закрытой дочерней MDI-формой, чтобы другие могли собирать мусор - но зачем мне это делать? Что происходит?

Кроме того, это немного уродливо, так как вы увидите мерцание открытия и закрытия формы, и это тоже кажется довольно хакерским.

Ответы [ 2 ]

3 голосов
/ 13 октября 2009

Технически, потому что Form является "FormerlyActiveMdiChild". Это похоже на ошибку. К счастью, не очень серьезный.

Способность устранять неисправности в несобранных объектах - хороший навык. Отладчик windbg от Microsoft, который поставляется с Debugging Tools for Windows (http://www.microsoft.com/whdc/devtools/debugging/default.mspx), отлично подходит для этой цели. В пошаговом руководстве ниже обратите внимание, что я удалил большую часть вывода из windbg, который не имеет отношения к делу.

  1. Вместо создания дочернего экземпляра MDI типа Form, создайте его подкласс как TestChildForm, чтобы его было легко идентифицировать.
  2. Запустите исполняемый файл и приложите windbg. Загрузите расширения .NET с помощью !loadby sos mscorwks.
  3. В windbg запустите !dumpheap -type TestChildForm.

     Address       MT     Size
    01e2e960 001c650c      320  
    
  4. Далее выполните !gcroot 01e2e960.

    ESP:3de7fc:Root:01e29a78(System.EventHandler)->
    01e26504(WindowsFormsApplication1.Form1)->
    01e269b8(System.Windows.Forms.PropertyStore)->
    01e2ef04(System.Windows.Forms.PropertyStore+ObjectEntry[])
    
  5. Далее запустите !dumparray -details 01e2ef04 и найдите в выводе 01e2e960.

          MT    Field   Offset                 Type VT     Attr    Value Name
    6797ea24  40032a3       10         System.Int16  1 instance       56 Key
    6797ea24  40032a4       12         System.Int16  1 instance        1 Mask
    6798061c  40032a5        0        System.Object  0 instance 01e2e960 Value1
    
  6. Наконец, я набрал !name2ee System.Windows.Forms.dll System.Windows.Forms.Form, затем !dumpclass 6604cb84 (как определено !name2ee) и искал 56.

          MT    Field   Offset                 Type VT     Attr    Value Name
    67982c4c  4001e80      fd8         System.Int32  1   static       56 PropFormerlyActiveMdiChild
    

Если вы предпочитаете использовать отладчик Visual Studio вместо windbg, вы должны сначала включить Свойства, Отладка, Включить отладку неуправляемого кода. Замените .load sos на .loadby sos mscorwks.

0 голосов
/ 18 марта 2010

Причина, по которой это происходит, довольно проста, но есть ссылка на эту форму. Хорошей новостью является то, что мы можем удалить эту ссылку.

Добавление обработчика событий для события закрытия формы.

private void ctlOpenMDI_Click(object sender, EventArgs e)
{
    Form newForm = new Form();
    newForm.FormClosing += new FormClosingEventHandler(form_Closing);
    newForm.MdiParent = this;
    newForm.Tag = new TestObject();
    newForm.Show();
}

И метод для обработки события.

private void form_Closing(object sender, EventArgs e)
{
    Form form = sender as Form;
    form.MdiParent = null;
}

Здесь мы сбрасываем свойство MdiParent, при этом форма удаляется из списка родительского MdiChild. Теперь, когда форма закрыта, эта ссылка также будет сброшена.

...