Я столкнулся с проблемами, когда элементы управления, выделяющие большие объемы памяти, уничтожаются и реконструируются с помощью событий input из пользовательского интерфейса.
Очевидно, что установка значения "Содержимое окна" равной null недостаточно для освобождения памяти, которую использовал содержащийся в ней элемент управления. В конце концов, он будет GCed. Но по мере того, как события input для уничтожения и создания элементов управления становятся все более частыми, GC, похоже, пропускает некоторые объекты.
Я разбил его на этот пример:
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" x:Name="window" MouseMove="window_MouseMove">
</Window>
C#:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
private class HeavyObject : UserControl
{
private byte[] x = new byte[750000000];
public HeavyObject()
{
for (int i = 0; i++ < 99999999; ) { } // just so we can follow visually in Process Explorer
Content = "Peekaboo!"; // change Content to el cheapo re-trigger MouseMove
}
}
public MainWindow()
{
InitializeComponent();
//Works 1:
//while (true)
//{
// window.Content = null;
// window.Content = new HeavyObject();
//}
}
private void window_MouseMove(object sender, MouseEventArgs e)
{
if (window.Content == null)
{
GC.Collect();
GC.WaitForPendingFinalizers();
// Works 2:
//new HeavyObject();
//window.Content = "Peekaboo!"; // change Content to el cheapo re-trigger MouseMove
//return;
window.Content = new HeavyObject();
}
else
{
window.Content = null;
}
}
}
}
При этом выделяется объект ~750 МБ (HeavyObject) при каждом событии MouseMove и помещается в качестве содержимого главного окна. Если курсор остается в окне, изменение содержимого будет бесконечно повторно инициировать событие. Ссылка на HeavyObject обнуляется перед его следующей конструкцией, и запускается тщетная попытка GC его остатков.
Что мы можем видеть в ProcessExplorer / Taskman, так это то, что иногда выделяется 1,5 ГБ или более. Если нет, переместите мышь дико и уходите и повторно-войдите в окно. Если вы компилируете для x86 без LARGEADDRESSAWARE, вы получите исключение OutOfMemoryException (ограничение ~1,3 ГБ).
Вы не столкнетесь с таким поведением, если вы либо выделите HeavyObject в бесконечном цикле без использования ввода с помощью мыши (раскомментируем раздел «Работает 1»), либо если вы активируете выделение с помощью ввода мыши, но не поместите объект в визуальное дерево (раскомментируем seciton «Works 2»).
Поэтому я предполагаю, что это как-то связано с визуальным деревом, лениво освобождающим ресурсы. Но есть еще один странный эффект: если 1,5 ГБ потребляется во время того, как курсор находится за пределами окна, кажется, что GC не вступят в силу, пока MouseMove не будет запущен снова. По крайней мере, потребление памяти, по-видимому, стабилизируется до тех пор, пока не запускаются дальнейшие события. Таким образом, либо где-то осталась долгоживущая ссылка, либо ГК становится ленивым, когда нет активности.
Мне это кажется достаточно странным. Можете ли вы понять, что происходит?
Редактировать: Как прокомментировал БаламБалам:Данные, лежащие в основе Контроля, могут быть разыгранище перед уничтожением Контроля. Это был бы план Б. Тем не менее, возможно, есть более общее решение.
Говорить, что такие элементы управления являются плохим кодом, бесполезно. Я хотел бы знать, почему объект, на который я не держу ссылку, получает GCed, если я оставляю пользовательский интерфейс в покое на несколько тиков после его разыменования, но задерживается навсегда, если другой (который должен был быть создан пользовательским вводом) немедленно займет его место.