2009-11-11 13 views
10

Tengo un problema de pérdida de memoria en mi aplicación que carga una gran cantidad de imágenes. Soy bastante nuevo en C#, y pensé que mis días de problemas de pérdida de memoria habían pasado. No puedo resolver el problema. ¿Tal vez estoy usando algunos módulos no administrados que no manejo correctamente?Fuga de memoria de carga de imagen con C#

Para ilustrar mi problema, he simplificado el núcleo de las causas del problema y lo he trasladado a un proyecto limpio. Tenga en cuenta que todo esto es un código tonto que no refleja la aplicación original de donde vino. En la aplicación de prueba tengo 2 botones, activando dos eventos.

Botón 1 - Crear: establecer un objeto en el contexto de datos. Esto cargará las imágenes y mantenerlos vivos estableciendo el objeto a la DataContext:

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

Botón 2 - Limpieza: Mi opinión es que si suelto de la retención de referencia del SillyImageLoader que a su vez mantiene las imágenes, a continuación, esto será eliminado También exploto explícitamente la recolección de basura solo para ver inmediatamente la cantidad de memoria después de eliminar la referencia.

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

Cuando estoy probando estoy cargando una imagen de 974KB jpeg. Tener 30 representaciones de mapas de bits de esto aumenta el uso de memoria de mi aplicación de ~ 18MB a ~ 562MB. De acuerdo. Pero cuando llego a la limpieza, la memoria solo baja a ~ 292MB. Si repito Create + CleanUp, me quedan otros ~ 250 MB de memoria. Entonces, obviamente, alguien todavía tiene algo.

Aquí es el SillyImageLoader-código:

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

¿Alguna idea? El problema parece ser con BitmapSource. Manteniendo solo el mapa de bits, no hay pérdida de memoria. Estoy usando BitmapSource para poder configurar esto en la propiedad de Origen de una Imagen. ¿Debo hacer esto de manera diferente? Si es así, aún me gustaría saber la respuesta de la fuga de memoria.

Gracias.

+0

¿Quién llama Deshace el BitmapSource devuelto por LoadImage? –

+0

Pensé que esto, de hecho publicó una respuesta basada en él, pero no puedo ver una disposición en BitmapSource (he eliminado la respuesta) –

+0

¿Cómo se está monitoreando el uso de la memoria de su aplicación? ¿Administrador de tareas? –

Respuesta

13

Cuando se llama a

bmp.GetHbitmap() 

se crea una copia del mapa de bits. Tendrá que mantener una referencia al puntero a ese objeto y llamar al

DeleteObject(...) 

en él.

De here:

Observaciones

Usted es responsable de llamar al método GDI DeleteObject para liberar la memoria utilizado por el objeto de mapa de bits GDI.


Es posible que pueda ahorrarse el dolor de cabeza (y por encima) de copiar el mapa de bits utilizando BitmapImage en lugar de BitmapSource. Esto le permite cargar y crear en un solo paso.

+0

¡Esto es absolutamente correcto y hace que los problemas de memoria desaparezcan! ¡Gracias! ¡Y gracias a los demás con respuestas similares! Tienes razón en que podría usar BitmapImage en este ejemplo en particular. Sin embargo, en mi aplicación actual no tengo una ruta a un archivo de imagen, pero obtengo mapas de bits de otro objeto de una biblioteca de terceros. No hay mucho que pueda hacer sobre esto. Entonces no puedo usar un Uri, necesito usar un mapa de bits. – stiank81

7

Debe llamar al método GDI DeleteObject en el puntero IntPtr devuelto por GetHBitmap(). El IntPtr devuelto por el método es un puntero a la copia del objeto en la memoria.Esto se debe liberar manualmente usando el siguiente código:

[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

Parece que cuando se llama a GetHBitmap() usted es responsable de liberar el objeto

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

que supongo que la BitmapSource no lo hace asumir la responsabilidad de liberar este objeto.