2009-11-11 10 views
10

Ho un problema di perdita di memoria nella mia applicazione che carica una grande quantità di immagini. Sono piuttosto nuovo di C# e ho pensato che i miei giorni di problemi di perdita di memoria fossero passati. Non riesco a capire il problema - forse sto usando alcuni moduli non gestiti che non sono gestiti correttamente?Perdita di memoria di caricamento dell'immagine con C#

Per illustrare il mio problema, ho semplificato il nucleo di ciò che causa il problema e lo ho spostato in un progetto pulito. Si noti che questo è tutto il codice sciocco che non riflette l'applicazione originale da cui proviene. Nell'applicazione di prova ho 2 pulsanti, attivando due eventi.

Pulsante 1 - Crea: impostazione di un oggetto nel datacontext. Questo caricherà le immagini e mantenerle in vita impostando l'oggetto al DataContext:

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

Pulsante 2 - CleanUp: La mia comprensione è che se ho lasciato andare il riferimento che tiene il SillyImageLoader che detiene ancora una volta le immagini, quindi questo sarà cancellato. Ho anche attivato esplicitamente la garbage collection solo per vedere immediatamente la quantità di memoria dopo aver lasciato cadere il riferimento.

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

Durante il test, sto caricando un'immagine jpeg da 974 KB. Contiene 30 rappresentazioni bitmap di questo aumenta l'utilizzo della memoria della mia applicazione da ~ 18 MB a ~ 562 MB. Ok. Ma quando premo cleanup la memoria scende solo a ~ 292 MB. Se ripeto Crea + CleanUp, mi rimane un altro ~ 250 MB di memoria. Quindi ovviamente qualcosa è ancora detenuto da qualcuno.

Ecco il SillyImageLoader-code:

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

Tutte le idee? Il problema sembra essere con BitmapSource. Tenendo solo la bitmap non c'è perdita di memoria. Sto usando BitmapSource per poterlo impostare sulla proprietà Source di un'immagine. Dovrei farlo diversamente? Se è così - mi piacerebbe ancora sapere la risposta la perdita di memoria.

Grazie.

+0

Chi sta chiamando Dispose su BitmapSource restituito da LoadImage? –

+0

Ho pensato che, in effetti, ho postato una risposta basata su di esso ma non riesco a vedere una disposizione su BitmapSource (ho cancellato la risposta) –

+0

Come stai monitorando l'utilizzo della memoria della tua app? Task manager? –

risposta

13

Quando si chiama

bmp.GetHbitmap() 

viene creata una copia della bitmap. Dovrai mantenere un riferimento al puntatore a quell'oggetto e chiamare

DeleteObject(...) 

su di esso.

Da here:

Osservazioni

Lei è responsabile per la chiamata al metodo GDI DeleteObject per liberare la memoria utilizzata dal oggetto bitmap GDI.


Si può essere in grado di risparmiare il mal di testa (e spese generali) di copiare il bitmap utilizzando BitmapImage invece di BitmapSource. Questo ti permette di caricare e creare in un unico passaggio.

+0

Questo è assolutamente corretto e ha fatto andare via i problemi di memoria! Grazie! E grazie agli altri con risposte simili! Hai ragione che potrei usare BitmapImage in questo particolare esempio. Tuttavia, nella mia applicazione effettiva non ho un percorso per un file immagine, ma prelevo Bitmap da un altro oggetto di una libreria di terze parti. Non c'è davvero molto che posso fare per questo. Quindi non posso usare un Uri - ho bisogno di usare una bitmap .. – stiank81

7

È necessario chiamare il metodo GDI sul puntatore IntPtr restituito da GetHBitmap(). Il IntPtr restituito dal metodo è un puntatore alla copia dell'oggetto in memoria.Questo deve essere liberata manualmente utilizzando il seguente codice:

[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

Sembra che quando si chiama GetHBitmap() sei responsabile di liberare l'oggetto

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

Sto indovinando che il BitmapSource non lo fa assumersi la responsabilità di liberare questo oggetto.