Утечка памяти при загрузке изображений с C # - PullRequest
10 голосов
/ 11 ноября 2009

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

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

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

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

Кнопка 2 - Очистка. Насколько я понимаю, если я отпущу ссылку, содержащую 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. Держа только Bitmap, утечка памяти отсутствует. Я использую BitmapSource, чтобы установить это свойство Source для Image. Должен ли я сделать это по-другому? Если так - я все равно хотел бы узнать ответ утечки памяти.

Спасибо.

Ответы [ 3 ]

13 голосов
/ 11 ноября 2009

при звонке

bmp.GetHbitmap()

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

DeleteObject(...)

на нем.

С здесь :

Примечания

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


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

7 голосов
/ 11 ноября 2009

Вам необходимо вызвать метод 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;
}
5 голосов
/ 11 ноября 2009

Кажется, что когда вы вызываете 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 не несет ответственности за освобождение этого объекта.

...