10

大量の画像をロードするアプリケーションにメモリ リークの問題があります。私は C# にかなり慣れていないので、メモリ リークの問題に悩まされていた日々は過ぎ去ったと思いました。問題がわかりません - 正しく処理されていない管理されていないモジュールを使用している可能性がありますか?

私の問題を説明するために、問題の原因の核心を単純化し、これをクリーンなプロジェクトに移動しました。これはすべて、元のアプリケーションを反映していないばかげたコードであることに注意してください。テスト アプリケーションには 2 つのボタンがあり、2 つのイベントをトリガーします。

ボタン 1 - 作成: オブジェクトを datacontext に設定します。これにより、画像が読み込まれ、オブジェクトを DataContext に設定することで画像が維持されます。

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

ボタン 2 - クリーンアップ: 画像を再び保持する SillyImageLoader を保持している参照を手放すと、これは削除されると理解しています。また、ガベージ コレクションを明示的にトリガーして、参照を削除した直後にメモリの量を確認します。

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

テスト時に、974KB の jpeg 画像をロードしています。これの 30 個のビットマップ表現を保持すると、アプリケーションのメモリ使用量が ~18MB から ~562MB に増加します。Ok。しかし、クリーンアップを実行すると、メモリは最大 292MB までしか減少しません。Create+CleanUp を繰り返すと、さらに 250MB のメモリが残ります。だから明らかに何かがまだ誰かによって保持されています。

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 にあるようです。ビットマップのみを保持すると、メモリ リークは発生しません。これを Image の Source プロパティに設定できるように BitmapSource を使用しています。これを別の方法で行う必要がありますか?もしそうなら、メモリリークの答えを知りたいです。

ありがとう。

4

3 に答える 3

13

電話すると

bmp.GetHbitmap()

ビットマップのコピーが作成されます。そのオブジェクトへのポインタへの参照を保持して呼び出す必要があります

DeleteObject(...)

その上で。

ここから:

備考

GDI の DeleteObject メソッドを呼び出して、GDI ビットマップ オブジェクトが使用するメモリを解放する必要があります。


BitmapSourceの代わりに BitmapImageを使用することで、ビットマップをコピーするという頭痛 (およびオーバーヘッド) を回避できる場合があります。これにより、ロードと作成を 1 ステップで行うことができます。

于 2009-11-11T12:31:26.067 に答える
7

GetHBitmap() から返さDeleteObjectれたポインターで GDI メソッドを呼び出す必要があります。IntPtrメソッドから返される は、メモリ内の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;
}
于 2009-11-11T12:35:23.767 に答える
5

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 は、このオブジェクトを解放する責任を負わないと思います。

于 2009-11-11T12:36:20.877 に答える