10
votes

Fuite de mémoire de chargement d'image avec C #

J'ai une question de fuite de mémoire dans mon application qui charge une grande quantité d'images. Je suis plutôt nouveau à C # et je pensais que mes journées de fuite de mémoire étaient passées. Je ne peux pas comprendre le problème - peut-être que j'utilise des modules non gérés que je ne manipule pas correctement?

illustrer mon problème, j'ai simplifié le noyau de ce qui provoque le problème et déplacé cela dans un projet propre. Notez que c'est tout le code idiot qui ne reflète pas l'application initiale. Dans l'application de test, j'ai 2 boutons, déclenchant deux événements. p>

bouton 1 - Créer: Définition d'un objet sur DataContext. Cela chargera les images et les gardera en vie en réglant l'objet sur le DataContext: p> xxx pré>

bouton 2 - Nettoyage: Ma compréhension est que si je laisse aller de la référence SILLYIMAGELOADER qui maintient à nouveau les images, cela sera supprimé. Je déclenche également explicitement la collecte des ordures juste pour voir immédiatement la quantité de mémoire après avoir chuté la référence. P>

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());
            }            
        }
    }
}


6 commentaires

Qui a appelé le bitmapsource est revenu de LoadImage?


Je pensais cela, en effet posté une réponse basée sur elle, mais je ne peux pas voir un éliminer sur Bitmapsource (j'ai supprimé la réponse)


Comment surveillez-vous l'utilisation de la mémoire de votre application? Gestionnaire des tâches?


Oui - Surveillance de la manière la plus primitive - à l'aide du gestionnaire de tâches. Il y a probablement de meilleurs outils là-bas, mais du moins dans ce cas, il fait le travail. Vous voulez suggérer un meilleur outil / méthode? :-)


Le responsable des tâches peut être peut être trompeur. Lorsque vous allouez un objet, .NET allouera une certaine mémoire - qui impliquera finalement demander à Windows d'allouer une certaine mémoire au processus. Cette mémoire fait partie de vos applications "Set de travail" - la mémoire totale allouée au processus. Lorsque vous publiez votre dernière référence à l'objet et que le GC libère, la mémoire du tas de CLR sera marquée comme libre, mais elle ne sera pas retournée pour une utilisation par Windows - elle reste une partie de l'ensemble de travail. Parce que Task Manager affiche la taille de votre ensemble de travail, vous pouvez obtenir une impression trompeuse de l'utilisation de la mémoire.


Sur les outils avant, je peux recommander votre fichier profileur ( Yourkit.com/.net/profiler/ Index.jsp ) Une licence personnelle est assez bon marché et contribue vraiment à suivre les ressources qui n'étaient pas libérées et quels objets y gardent des références.


3 Réponses :


13
votes

Lorsque vous appelez xxx

une copie du bitmap est créée. Vous aurez besoin de conserver une référence au pointeur à cet objet et d'appeler xxx

dessus.

de ici :

remarques

vous êtes responsable d'appeler le GDI DeleteObject Méthode pour libérer le mémoire utilisée par l'objet bitmap GDI.


Vous pouvez être capable de sauvegarder vous-même le mal de tête (et la tête) de copier le bitmap en utilisant bitmapimage au lieu de bitmapsource. Cela vous permet de charger et de créer en une étape.


1 commentaires

Ceci est absolument correct et rendu les problèmes de mémoire disparaître! THX! Et thx aux autres avec des réponses similaires! Vous avez raison pour que je puisse utiliser bitmapimage dans cet exemple particulier. Cependant, dans mon application réelle, je n'ai pas de chemin d'accès à un fichier image, mais fetch bitmaps d'un autre objet d'une bibliothèque tierce. Il n'y a pas vraiment beaucoup que je puisse faire à ce sujet. Donc, je ne peux pas utiliser une URI - j'ai besoin d'utiliser un bitmap ..



7
votes

Vous devez appeler la méthode GDI deleteObject code> sur le pointeur code> intPTR code> renvoyé de gethbitmap (). Le intPTR code> renvoyé de la méthode est un pointeur à la copie de l'objet en mémoire. Ceci doit être libéré manuellement en utilisant le code suivant:

[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;
}


0 commentaires

5
votes

Il semble que lorsque vous appelez gethbitmap (), vous êtes responsable de libérer l'objet

[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);
}


0 commentaires