Ошибка загрузки памяти изображения с помощью С#

У меня проблема с утечкой памяти в моем приложении, которая загружает большое количество изображений. Я новичок в С#, и думал, что мои дни проблемы с утечкой памяти прошли. Я не могу понять проблему - может быть, я использую некоторые неуправляемые модули, которые я неправильно обрабатываю?

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

Кнопка 1 - Создать: установка объекта в datacontext. Это загрузит изображения и сохранит их, установив объект в DataContext:

var imgPath = @"C:\some_fixed_path\img.jpg";
DataContext = new SillyImageLoader(imgPath);

Кнопка 2 - CleanUp: Я понимаю, что если я отпущу ссылку, содержащую SillyImageLoader, которая снова будет содержать изображения, тогда это будет удалено. Я также явно запускаю сборку мусора, чтобы сразу увидеть объем памяти после сброса ссылки.

DataContext = null; 
System.GC.Collect();

При тестировании я загружаю jpeg-изображение 974 Кбайт. Сохранение 30 растровых изображений, что увеличивает использование памяти моего приложения с ~ 18 МБ до ~ 562 МБ. ОК. Но когда я удаляю очистку, память падает только до ~ 292 МБ. Если я повторяю Create + CleanUp, я остаюсь с другой памятью ~ 250 МБ. Так что, очевидно, что-то все еще держит кто-то.

Вот код SillyImageLoader:

namespace MemoryLeakTest
{
    using System;
    using System.Drawing;
    using System.Windows;
    using System.Windows.Interop;
    using System.Windows.Media.Imaging;

    public class SillyImageLoader
    {
        private BitmapSource[] _images; 

        public SillyImageLoader(string path)
        {
            DummyLoad(path);
        }

        private void DummyLoad(string path)
        {
            const int numberOfCopies = 30;
            _images = new BitmapSource[numberOfCopies];

            for (int i = 0; i < numberOfCopies; i++)
            {
                _images[i] = LoadImage(path);
            }
        }

        private static BitmapSource LoadImage(string path)
        {
            using (var bmp = new Bitmap(path))
            {
                return Imaging.CreateBitmapSourceFromHBitmap(
                    bmp.GetHbitmap(),
                    IntPtr.Zero,
                    Int32Rect.Empty,
                    BitmapSizeOptions.FromEmptyOptions());
            }            
        }
    }
}

Любые идеи? Проблема, похоже, связана с BitmapSource. Удерживая только битмап, утечки памяти нет. Я использую BitmapSource, чтобы иметь возможность установить это свойство Source для изображения. Должен ли я делать это по-другому? Если так - я все равно хотел бы узнать ответ на утечку памяти.

Спасибо.

Ответ 1

Когда вы вызываете

bmp.GetHbitmap()

создается копия растрового изображения. Вам нужно будет сохранить ссылку на указатель на этот объект и вызвать

DeleteObject(...)

на нем.

От здесь:

Примечание

Вы несете ответственность за вызов Метод GDI DeleteObject для освобождения памяти, используемой объектом растрового изображения GDI.


Вы можете сэкономить головную боль (и накладные расходы) на копирование растрового изображения, используя BitmapImage вместо BitmapSource. Это позволяет загружать и создавать за один шаг.

Ответ 2

Вам нужно вызвать метод GDI DeleteObject на указателе IntPtr, возвращенном из GetHBitmap(). Возвращаемый метод IntPtr является указателем на копию объекта в памяти. Это необходимо вручную освободить, используя следующий код:

[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);

private static BitmapSource LoadImage(string path)
{

    BitmapSource source;
    using (var bmp = new Bitmap(path))
    {

        IntPtr hbmp = bmp.GetHbitmap();
        source = Imaging.CreateBitmapSourceFromHBitmap(
            hbmp,
            IntPtr.Zero,
            Int32Rect.Empty,
            BitmapSizeOptions.FromEmptyOptions());

        DeleteObject(hbmp);

    }

    return source;
}

Ответ 3

Кажется, что при вызове GetHBitmap() вы несете ответственность за освобождение объекта

[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);

private void DoGetHbitmap() 
{
    Bitmap bm = new Bitmap("Image.jpg");
    IntPtr hBitmap = bm.GetHbitmap();

    DeleteObject(hBitmap);
}

Я предполагаю, что BitmapSource не несет ответственности за освобождение этого объекта.