Сборка мусора не может восстановить 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 в объем памяти.

То, что работало для меня, было:

  1. Установите для ImageSource элемента управления Image значение null
  2. Запустите UpdateLayout() перед удалением элемента управления, содержащего изображение из пользовательского интерфейса.
  3. Убедитесь, что вы 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 не сможет вернуть его.

Другие вопросы по тегам