В WPF: Children.Remove или Children.Clear не освобождает объекты

Обновление: я попробовал это на другом, более чисто установленном компьютере. Я не мог воспроизвести это на этой машине. Если я узнаю, что вызывает оскорбительный компонент (VSStudio), я дам вам знать.

Я создаю некоторые UIElements из кода позади и ожидал сбор мусора, чтобы очистить материал. Тем не менее, объекты не были свободными в то время, когда я ожидал этого. Я ожидал, что они будут освобождены в RemoveAt (0), но они будут освобождены только в конце программы.

Как я могу освободить объекты при удалении из коллекции "Дети" на холсте?

<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
    MouseDown="Window_MouseDown">
  <Grid>
    <Canvas x:Name="main" />
  </Grid>
</Window>

Код позади:

public partial class MainWindow : Window
{
  public MainWindow()
  {
    InitializeComponent();
  }

private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
  GC.Collect(); // This should pick up the control removed at a previous MouseDown
  GC.WaitForPendingFinalizers(); // Doesn't help either

  if (main.Children.Count == 0)
    main.Children.Add(new MyControl() { Background = Brushes.Yellow, Width = 100, Height = 50 });
  else
    main.Children.RemoveAt(0);
 }
}

public class MyControl : UserControl
{
  ~MyControl()
  {
    Debug.WriteLine("Goodbye");
  }
}

Ответ 1

Измените

public class MyControl : UserControl

to

public class MyControl : ContentControl

и он попрощается (после второго раза вы удалите элемент управления.) Я также проверил, что память не протекает, используя

Debug.WriteLine("mem: " + GC.GetTotalMemory(true).ToString());

Также см. this:

Вы удаляете TestControl, очищая grid.Children, но он не сразу подходит для сбора мусора. Несколько асинхронных операций над ним ожидаются, и он не может быть GC'd до тех пор, пока эти операции не завершатся (к ним относятся повышение события Unloaded и некоторый код очистки в механизме рендеринга).

Я проверил, что если вы дождитесь завершения этих операций (скажем, планируя операцию Dispatcher при приоритете ContextIdle), TestControl будет иметь право на GC, независимо от наличия привязки в TextBlock.

Пользователь UserControl должен либо иметь внутреннее событие, которое не очищается быстро, либо может быть ошибкой с VS2010 RC. Я бы сообщил об этом через соединение, но пока переключился на ContentControl.

Поскольку вы используете UserControl, я предполагаю, что вам также придется переключиться на использование шаблона Generic.xaml. Это не слишком сложно для перехода (и для большинства вещей это лучшее решение.)

Ответ 2

Объекты в С# не автоматически "освобождаются", как только они больше не используются.

Скорее, когда вы удаляете объект из своего элемента управления, он становится пригодным для сбора мусора в этой точке, предполагая, что у вас нет других ссылок на этот элемент UIelement.

Как только объект "отключен" (нет прямых или косвенных ссылок с любого используемого объекта в вашем приложении), он становится пригодным для сбора. Затем сборщик мусора, в конце концов, очистит ваш объект, но когда это произойдет, это не то, что вы (обычно) контролируете.

Просто верьте, что в конечном итоге он будет очищен. Это красавица С# (и .NET в целом) - управление и беспокойство, это обрабатывается для вас.


Изменить: после некоторого тестирования, кажется, что окно содержит ссылку на UIelement до следующего перехода макета. Вы можете заставить это произойти, добавив вызов:

this.UpdateLayout();

После удаления элемента (ов) из холста Children. Это приведет к тому, что объекты будут доступны для GC.

Ответ 3

В С# существует 3 поколения коллекции мусора, поэтому даже если ссылок на ваши объекты нет, для их освобождения могут потребоваться 3 сборки мусора.

Вы можете использовать параметр GC.Collect() для принудительной сборки мусора третьего поколения,
тем не менее, лучший подход - не называть GC.Collect() самостоятельно,
вместо этого используйте интерфейс IDisposable и привяжите Children к ObservableCollection и когда вы получаете событие CollectionChanged Dispose() любых удаленных объектов.

Ответ 4

Вы можете найти это. Недавно я узнал, что расширение разметки x: Name хранит ссылку на UIElement в родительском элементе управления в словаре, введенном под номером строки.

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

Там сообщение в блоге/видео отладка утечки памяти здесь: WPF x: Устранение утечки памяти

Решение состоит в том, чтобы не использовать x: Name или гарантировать, что элементы управления, которые хранятся в x: Name, очищаются, чтобы не потреблять слишком много памяти до того, как будет собрана часть визуального дерева.

Обновление: Вы отменяете регистрационный класс с помощью NameScope

this.grid.Children.Remove(child); // Remove the child from visual tree
NameScope.GetNameScope(this).UnregisterName("child"); // remove the keyed name
this.child = null; // null the field

// Finally it is free to be collected! 

Ответ 5

У нас была та же проблема, и мы подумали, что это может быть причиной. Но мы проанализировали наш проект с использованием инструментов профилирования памяти и обнаружили, что нет ничего общего с Grid.Remove или Grid.RemoveAt. Поэтому я думаю, что мое предложение - это просто посмотреть ваш проект в инструменте профилирования памяти и посмотреть, что происходит внутри вашего проекта. надеется, что это поможет.