Подтвердить что ты не робот

Правильное удаление и удаление ссылок на UserControls, чтобы избежать утечки памяти

Я разрабатываю приложение Windows Forms (.NET 4.0) в С# с помощью Visual С# express 2010. У меня проблема с освобождением памяти, выделенной для UserControls, которую я больше не использую.

Проблема:

У меня есть FlowLayoutPanel, где отображаются пользовательские UserControls. FlowLayoutPanel отображает результаты поиска и т.д., Поэтому коллекция отображаемых элементов UserControls должна быть повторно обновлена.

Прежде чем каждый новый набор этих UserControls будет создан и отображен, Dispose() вызывается во всех элементах управления, которые в настоящее время содержатся в моем элементе управления FlowLayoutPanel ControlCollection (Controls), тогда Clear() вызывается в том же ControlCollection.

Этого недостаточно, чтобы избавиться от ресурсов, используемых UserControls, потому что с каждым новым набором UserControls, который был создан и добавлен в мой ControlCollection, , и мои UserControls, похоже, не утверждаются сборкой мусора. Использование памяти приложения резко увеличивается в течение короткого периода времени, а затем достигает плато, пока не отобразится другой список. Я также проанализировал свое приложение с помощью .NET Memory Profiler, в котором сообщается о количестве возможных утечек памяти (см. Раздел ниже).

То, что я думаю, идет не так:

Я был неправ. Проблема заключалась в ошибке, вызванной использованием конструкции foreach для итерации через ControlCollection и вызова Dispose() на своих элементах управления, которые Ханс Пассант описывает в своем ответе.


Проблема, похоже, вызвана ToolTip, используемой в моих UserControls. Когда я удалил их, мои UserControls, похоже, были заявлены сборкой мусора. Это было подтверждено профилировщиком .NET. Проблема 1 и 6 из моего предыдущего теста (см. Нижнюю часть) больше не появилась, и она сообщила о новой проблеме:

Нераспределенные экземпляры (освободить ресурс и удалить внешние ссылки) В 7 типах есть экземпляры, которые были собраны мусором без надлежащего распоряжения. Изучите приведенные ниже типы для получения дополнительной информации.

ChoiceEditPanel (унаследованный), NodeEditPanel (унаследованный), Button, FlowLayoutPanel, Label, > Panel, TextBox

Даже при отсутствии ссылки на ToolTip, которая не является долгосрочным решением, все еще существует проблема детерминированного использования моего UserControls, когда я их больше не нуждаюсь. Однако это не так важно, как удаление ссылок на подсказки.

Код и более подробная информация

Я использую UserControl с именем NodesDisplayPanel, который действует как обертка для FlowLayoutPanel. Вот метод в моем классе NodesDisplayPanel, который используется для очистки всех элементов управления из моей FlowLayoutPanel:

public void Clear() {
    foreach (Control control in flowPanel.Controls) {
        if (control != NodeEditPanel.RootNodePanel) {
            control.Dispose();
        }
    }
    flowPanel.Controls.Clear();
    // widthGuide is used to control the widths of the Controls below it,
    // which have Dock set to Dockstyle.Top
    widthGuide = new Panel();
    widthGuide.Location = new Point(0, 0);
    widthGuide.Margin = new Padding(0);
    widthGuide.Name = "widthGuide";
    widthGuide.Size = new Size(809, 1);
    widthGuide.TabIndex = 0;
    flowPanel.Controls.Add(widthGuide);
}

Эти методы используются для добавления элементов управления:

public void AddControl(Control control) {
    flowPanel.Controls.Add(control);
}
public void AddControls(Control[] controls) {
    flowPanel.Controls.AddRange(controls);
}

Вот метод, который запускает новые NodeEditPanels и добавляет их в мою FlowLayoutPanel через мою NodesDisplayPanel. Этот метод из ListNodesPanel (как показано на скриншоте ниже), один из нескольких UserControls, которые создают экземпляр и добавляют NodeEditPanels:

public void UpdateNodesList() {
    Node[] nodes = Data.Instance.Nodes;
    Array.Sort(nodes,(IComparer<Node>) comparers[orderByDropDownList.SelectedIndex]);
    if ((listDropDownList.SelectedIndex == 1)
        && (nodes.Length > numberOfNodesNumUpDown.Value)) {
        Array.Resize(ref nodes,(int) numberOfNodesNumUpDown.Value);
    }
    NodeEditPanel[] nodePanels = new NodeEditPanel[nodes.Length];
    for (int index = 0; index < nodes.Length; index ++) {
        nodePanels[index] = new NodeEditPanel(nodes[index]);
    }
    nodesDisplayPanel.Clear();
    nodesDisplayPanel.AddControls(nodePanels);
}

Это мой пользовательский метод innitilization для моего ListNodesPanel UserControl. Надеемся, что метод UpdateNodesList() станет более понятным:

private void NonDesignerInnitialisation() {
    this.Dock = DockStyle.Fill;
    listDropDownList.SelectedIndex = 0;
    orderByDropDownList.SelectedIndex = 0;
    numberOfNodesNumUpDown.Enabled = false;
    comparers = new IComparer<Node>[3];
    comparers[0] = new CompareNodesByID();
    comparers[1] = new CompareNodesByNPCText();
    comparers[2] = new CompareNodesByChoiceCount();
}

В случае возникновения каких-либо известных проблем с конкретными компонентами Windows.Forms, Здесь перечислены все типы компонентов, которые используются в каждом из моих UserControls:

ChoiceEditPanel:

  • Панель
  • Этикетка
  • Кнопка
  • TextBox
  • ToolTip

NodeEditPanel

  • ChoiceEditPanel
  • FlowLayoutPanel
  • Панель
  • Этикетка
  • Кнопка
  • Textbox
  • ToolTip

Я также использую библиотеку i00SpellCheck для некоторых текстовых блоков

Возможные проблемы, изначально описанные .NET Memory Profiler:

Я получил мое приложение для отображения 50 или около того NodeEditPanels, дважды, второй список, имеющий идентичные значения для первого, но являющиеся разными экземплярами..Net Memory Profiler сравнивает состояния приложения после первой и второй операций и создает список возможных проблем:

  • Прямые корни EventHandler
    У одного типа есть экземпляры, которые непосредственно связаны с EventHandler. Это может указывать на то, что EventHandler не был удален правильно. Изучите тип ниже для получения дополнительной информации.

    ToolTip

  • Утилизированные экземпляры
    2 типа имеют экземпляры, которые были удалены, но не GCed. Изучите приведенные ниже типы для получения дополнительной информации.

    System.Drawing.Graphics, WindowsFont

  • Нераспределенные экземпляры (ресурс релиза)
    6 типов имеют экземпляры, которые были собраны мусором без надлежащего распоряжения. Изучите приведенные ниже типы для получения дополнительной информации.

    System.Drawing.Bitmap, System.Drawing.Font, System.Drawing.Region, Control.FontHandleWrapper, Cursor, WindowsFont

  • Прямые корни делегатов
    2 типа имеют экземпляры, которые непосредственно связаны с делегатом. Это может указывать на то, что делегат не был удален. Изучите приведенные ниже типы для получения дополнительной информации.

    System.__ Filters, __Filters

  • Прикрепленные экземпляры
    2 типа имеют экземпляры, которые закреплены в памяти. Изучите приведенные ниже типы для получения дополнительной информации.

    System.Object, System.Object []

  • Непрямые корни EventHandler
    53 типа имеют экземпляры, которые косвенно связаны с EventHandler. Это может указывать на то, что EventHandler не был удален правильно. Изучите приведенные ниже типы для получения дополнительной информации.

    ChoiceEditPanel, NodeEditPanel, ArrayList, Hashtable, Hashtable.bucket [], Hashtable.KeyCollection, Container, Container.Site, EventHandlerList, (...)

  • Нераспределенные экземпляры (использование памяти/ресурсов)
    3 типа имеют экземпляры, которые были собраны в мусор, не будучи надлежащим образом удалены. Изучите приведенные ниже типы для получения дополнительной информации.

    System.IO.BinaryReader, System.IO.MemoryStream, UnmanagedMemoryStream

  • Повторяющиеся экземпляры
    71 тип имеет дубликаты экземпляров (492 набора, 741 229 дублированных байтов). Повторяющиеся экземпляры могут вызвать ненужное потребление памяти. Изучите приведенные ниже типы для получения дополнительной информации.

    GPStream (8 наборов, 318 540 дублированных байтов), PropertyStore.IntegerEntry [] (24 набора, 93 092 дублированных байта), PropertyStore (10 наборов, 53 312 дублированных байтов), PropertyStore.SizeWrapper(16 наборов, 41,232 дублированных байта), PropertyStore.PaddingWrapper(8 наборов, 38 724 дублированных байта), PropertyStore.RectangleWrapper(28 наборов, 32 352 дублированных байта), PropertyStore.ColorWrapper(13 наборов, 30 216 дублированных байтов), System.Byte [] (3 набора, 25 622 дублированных байта), ToolTip.TipInfo(10 наборов, 21 056 дублированных байтов), Hashtable (2 набора, 20 148 дублированных байтов), (...)

  • Пустая слабая ссылка
    Тип WeakReference имеет экземпляры, которые больше не являются живыми. Изучите тип WeakReference для получения дополнительной информации.

    System.WeakReference

  • Нераспределенные экземпляры (четкие ссылки)
    У одного типа есть экземпляры, которые были собраны в мусор, не будучи надлежащим образом удалены. Изучите тип ниже для получения дополнительной информации.

    EventHandlerList

  • Крупные экземпляры
    2 типа имеют экземпляры, которые расположены в кучке большого объекта. Изучите приведенные ниже типы для получения дополнительной информации.

    Dictionary.DictionaryItem [], System.Object []

  • Сохраненные дубликаты экземпляров
    25 типов имеют повторяющиеся экземпляры, которые хранятся в других повторяющихся экземплярах (136 наборов, 371 766 дублированных байтов). Изучите приведенные ниже типы для получения дополнительной информации.

    System.IO.MemoryStream(8 наборов, 305 340 дублированных байтов), System.Byte [] (7 наборов, 248 190 дублированных байтов), PropertyStore.ObjectEntry [] (10 наборов, 40 616 дублированных байтов), Hashtable.bucket [] (2 набора, 9,696 дублированных байтов), System.String(56 наборов, 8 482 дублированных байта), EventHandlerList.ListEntry(6 наборов, 4 072 дублированных байта), List (6 наборов, 4 072 дублированных байта), EventHandlerList (3 набора, 3 992 дублированных байты), System.EventHandler(6 наборов, 3,992 дублированных байта), DialogueEditor.Choice [] (6 наборов, 3992 дублированных байта), (...)

4b9b3361

Ответ 1

foreach (Control control in flowPanel.Controls) {
    if (control != NodeEditPanel.RootNodePanel) {
        control.Dispose();
    }
}
flowPanel.Controls.Clear();

Это довольно классическая ошибка Winforms, многие программисты ее укусили. Утилизация элемента управления также удаляет его из родительской коллекции Control. Большинство классов коллекции .NET запускают InvalidOperationException, когда итерация их изменяет коллекцию, но это не было сделано для класса ControlCollection. Эффект заключается в том, что ваш цикл foreach пропускает элементы, он использует только элементы с четным номером.

Вы уже обнаружили проблему, но значительно ухудшили ее, вызвав Controls.Clear(). Экстра-специально противно, потому что сборщик мусора не будет завершать элементы управления, которые удаляются таким образом. После создания собственного дескриптора окна для элемента управления он будет ссылаться на внутреннюю таблицу, которая отображает ручки Window для элементов управления. Только уничтожение собственного окна удаляет ссылку из этой таблицы. Этого никогда не бывает в коде вроде этого, вызов Dispose() - это жесткое требование. Очень необычно в .NET.

Решение состоит в том, чтобы отбирать коллекцию Controls назад, чтобы элементы управления отсутствием влияния на то, что вы повторяете. Вот так:

for (int ix = flowPanel.Controls.Count-1; ix >= 0; --ix) {
    var ctl = flowPanel.Controls[ix];
    if (ctl != NodeEditPanel.RootNodePanel) ctl.Dispose();
}