Сборка мусора не может восстановить BitmapImage?
У меня есть приложение (WPF), которое создает BitmapImages в огромных количествах (например, 25000). Похоже, что фреймворк использует некоторую внутреннюю логику, поэтому после создания используется около 300 МБ памяти (150 виртуальных и 150 физических). Эти BitmapImages добавляются в объект Image, и они добавляются в Canvas. Проблема в том, что когда я выпускаю все эти изображения, память не освобождается. Как мне вернуть память обратно?
Приложение простое: Xaml
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Canvas x:Name="canvas" Grid.ColumnSpan="2"></Canvas>
<Button Content="Add" Grid.Row="1" Click="Button_Click"/>
<Button Content="Remove" Grid.Row="1" Grid.Column="1" Click="Remove_click"/>
</Grid>
Код-за
const int size = 25000;
BitmapImage[] bimages = new BitmapImage[size];
private void Button_Click(object sender, RoutedEventArgs e)
{
var paths = Directory.GetFiles(@"C:\Images", "*.jpg");
for (int i = 0; i < size; i++)
{
bimages[i] = new BitmapImage(new Uri(paths[i % paths.Length]));
var image = new Image();
image.Source = bimages[i];
canvas.Children.Add(image);
Canvas.SetLeft(image, i*10);
Canvas.SetTop(image, i * 10);
}
}
private void Remove_click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < size; i++)
{
bimages[i] = null;
}
canvas.Children.Clear();
bimages = null;
GC.Collect();
GC.Collect();
GC.Collect();
}
Это скриншот ResourceManager после добавления изображений
5 ответов
В Wpf была ошибка, из-за которой нас укусили, когда объекты BitmapImage не освобождаются, если вы их не заморозите. http://blogs.msdn.com/b/jgoldb/archive/2008/02/04/finding-memory-leaks-in-wpf-based-applications.aspx была исходной страницей, где мы обнаружили проблему. Это должно было быть исправлено в Wpf 3.5 sp1, но мы все еще видели это в некоторых ситуациях. Попробуйте изменить свой код следующим образом, чтобы увидеть, если это проблема:
bimages[i] = new BitmapImage(new Uri(paths[i % paths.Length]));
bimages[i].Freeze();
Мы обычно замораживаем наши объекты BitmapImage сейчас, когда мы видели другие экземпляры в профилировщике, где Wpf прослушивал события в BitmapImage и тем самым поддерживал изображение в живых.
Если вызов Feeze() не является очевидным исправлением для вашего кода, я настоятельно рекомендую использовать профилировщик, такой как RedGate Memory Profiler, который будет отслеживать дерево зависимостей, которое покажет вам, что именно хранит ваши объекты Image в объем памяти.
То, что работало для меня, было:
- Установите для ImageSource элемента управления Image значение null
- Запустите UpdateLayout() перед удалением элемента управления, содержащего изображение из пользовательского интерфейса.
- Убедитесь, что вы Freeze() BitmapImage при его создании и что не было никаких неслабых ссылок на объекты BitmapImage, используемые в качестве ImageSources.
Мой метод очистки для каждого изображения оказался таким простым:
img.Source = null;
UpdateLayout();
Я смог прийти к этому путем экспериментов, сохранив список с объектом WeakReference(), указывающим на каждый созданный мной BitmapImage, и затем проверил поле IsAlive в WeakReferences после того, как они должны были быть очищены, чтобы подтвердить, что они ' Я на самом деле был убран.
Итак, мой метод создания BitmapImage выглядит так:
var bi = new BitmapImage();
using (var fs = new FileStream(pic, FileMode.Open))
{
bi.BeginInit();
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.StreamSource = fs;
bi.EndInit();
}
bi.Freeze();
weakreflist.Add(new WeakReference(bi));
return bi;
Я следовал за ответом, данным AAAA. Оригинальный код, вызывающий заполнение памяти:
if (overlay != null) overlay.Dispose();
overlay = new Bitmap(backDrop);
Graphics g = Graphics.FromImage(overlay);
Вставлен блок кода AAAA, в C# добавлено "using System.Threading;" и VB добавить "Imports System.Threading":
if (overlay != null) overlay.Dispose();
//--------------------------------------------- code given by AAAA
Thread t = new Thread(new ThreadStart(delegate
{
Thread.Sleep(500);
GC.Collect();
}));
t.Start();
//-------------------------------------------- \code given by AAAA
overlay = new Bitmap(backDrop);
Graphics g = Graphics.FromImage(overlay);
Повторяющееся зацикливание этого блока теперь обеспечивает постоянный и низкий объем памяти. Этот код работал с использованием Visual Studio 2015 Community.
Это все еще массив
BitmapImage[] bimages = new BitmapImage[size];
Массивы - это непрерывные структуры данных фиксированной длины, после выделения памяти для всего массива вы не сможете восстановить его части. Попробуйте использовать другие структуры данных (например, LinkedList<T>
) или другое более подходящее в вашем случае
Я просто рассказываю о своем опыте восстановления памяти BitmapImage. Я работаю с.Net Framework 4.5.
Я создаю простое приложение WPF и загружаю большой файл изображения. Я попытался очистить изображение из памяти, используя следующий код:
private void ButtonImageRemove_Click(object sender, RoutedEventArgs e)
{
image1.Source = null;
GC.Collect();
}
Но это не сработало. Я пробовал и другие решения, но не получил ответ. После нескольких дней борьбы я обнаружил, что если я дважды нажму кнопку, GC освободит память. затем я просто пишу этот код для вызова сборщика GC через несколько секунд после нажатия кнопки.
private void ButtonImageRemove_Click(object sender, RoutedEventArgs e)
{
image1.Source = null;
System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(delegate
{
System.Threading.Thread.Sleep(500);
GC.Collect();
}));
thread.Start();
}
этот код только что протестирован в DotNetFr 4.5. возможно, вам придется заморозить объект BitmapImage для более низкого.Net Framework .
редактировать
Этот код не работает, если макет не будет обновлен. Я имею в виду, если родительский контроль будет удален, GC не сможет вернуть его.