2010-03-31 4 views
3

J'ai écrit une petite application WPF avec 2 threads - le thread principal est le thread graphique et un autre thread est worker.
L'application a un formulaire WPF avec quelques contrôles. Il y a un bouton, permettant de sélectionner le répertoire. Après avoir sélectionné le répertoire, l'application recherche les fichiers .jpg dans ce répertoire et vérifie si leurs vignettes sont en hachage. s'ils le sont, cela ne fait rien. sinon, il ajoute leurs noms de fichiers complets pour faire la queue pour le travailleur.
Travailleur prend des noms de fichiers de cette file, en chargeant des images JPEG (en utilisant JpegBitmapDecoder et BitmapFrame de WPF), en en faisant des miniatures (en utilisant TransformedBitmap de WPF) et en les ajoutant à hashtable.
Tout fonctionne bien, sauf la consommation de mémoire par cette application lors de la création de vignettes pour les grandes images (comme 5000x5000 pixels). J'ai ajouté des zones de texte sur mon formulaire pour montrer la consommation de mémoire (GC.GetTotalMemory() et Process.GetCurrentProcess(). PrivateMemorySize64) et a été très surpris, cuz GC.GetTotalMemory() reste près de 1-2 Mo, tandis que la taille de la mémoire privée grandit constamment, en particulier lors du chargement de la nouvelle image (~ + 100Mb par image).
Même après avoir chargé toutes les images, créé des miniatures et libéré des images d'origine, la taille de la mémoire privée reste entre ~ 700 et 800 Mo. Mon VirtualBox est limité à 512 Mo de mémoire physique et Windows dans VirtualBox commence à échanger beaucoup pour gérer cette énorme consommation de mémoire. Je suppose que je fais quelque chose de mal, mais je ne sais pas comment enquêter sur ce problème, car selon GC, la taille de la mémoire allouée est très faible.L'application C# WPF utilise trop de mémoire alors que GC.GetTotalMemory() est faible

Code Fixation de la classe chargeur miniature:

class ThumbnailLoader 
{ 
    Hashtable thumbnails; 
    Queue<string> taskqueue; 
    EventWaitHandle wh; 
    Thread[] workers; 
    bool stop; 
    object locker; 
    int width, height, processed, added; 

    public ThumbnailLoader() 
    { 
     int workercount,i; 
     wh = new AutoResetEvent(false); 
     thumbnails = new Hashtable(); 
     taskqueue = new Queue<string>(); 
     stop = false; 
     locker = new object(); 
     width = height = 64; 
     processed = added = 0; 
     workercount = Environment.ProcessorCount; 
     workers=new Thread[workercount]; 
     for (i = 0; i < workercount; i++) { 
      workers[i] = new Thread(Worker); 
      workers[i].IsBackground = true; 
      workers[i].Priority = ThreadPriority.Highest; 
      workers[i].Start(); 
     } 
    } 

    public void SetThumbnailSize(int twidth, int theight) 
    { 
     width = twidth; 
     height = theight; 
     if (thumbnails.Count!=0) AddTask("#resethash"); 
    } 

    public void GetProgress(out int Added, out int Processed) 
    { 
     Added = added; 
     Processed = processed; 
    } 

    private void AddTask(string filename) 
    { 
     lock(locker) { 
      taskqueue.Enqueue(filename); 
      wh.Set(); 
      added++; 
     } 
    } 

    private string NextTask() 
    { 
     lock(locker) { 
      if (taskqueue.Count == 0) return null; 
      else { 
       processed++; 
       return taskqueue.Dequeue(); 
      } 
     } 
    } 

    public static string FileNameToHash(string s) 
    { 
     return FormsAuthentication.HashPasswordForStoringInConfigFile(s, "MD5"); 
    } 

    public bool GetThumbnail(string filename,out BitmapFrame thumbnail) 
    { 
     string hash; 
     hash = FileNameToHash(filename); 
     if (thumbnails.ContainsKey(hash)) { 
      thumbnail=(BitmapFrame)thumbnails[hash]; 
      return true; 
     } 
     AddTask(filename); 
     thumbnail = null; 
     return false; 
    } 

    private BitmapFrame LoadThumbnail(string filename) 
    { 
     FileStream fs; 
     JpegBitmapDecoder bd; 
     BitmapFrame oldbf, bf; 
     TransformedBitmap tb; 
     double scale, dx, dy; 
     fs = new FileStream(filename, FileMode.Open); 
     bd = new JpegBitmapDecoder(fs, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); 
     oldbf = bd.Frames[0]; 
     dx = (double)oldbf.Width/width; 
     dy = (double)oldbf.Height/height; 
     if (dx > dy) scale = 1/dx; 
     else scale = 1/dy; 
     tb = new TransformedBitmap(oldbf, new ScaleTransform(scale, scale)); 
     bf = BitmapFrame.Create(tb); 
     fs.Close(); 
     oldbf = null; 
     bd = null; 
     GC.Collect(); 
     return bf; 
    } 

    public void Dispose() 
    { 
     lock(locker) { 
      stop = true; 
     } 
     AddTask(null); 
     foreach (Thread worker in workers) { 
      worker.Join(); 
     } 
     wh.Close(); 
    } 

    private void Worker() 
    { 
     string curtask,hash; 
     while (!stop) { 
      curtask = NextTask(); 
      if (curtask == null) wh.WaitOne(); 
      else { 
       if (curtask == "#resethash") thumbnails.Clear(); 
       else { 
        hash = FileNameToHash(curtask); 
        try { 
         thumbnails[hash] = LoadThumbnail(curtask); 
        } 
        catch { 
         thumbnails[hash] = null; 
        } 
       } 
      } 
     } 
    } 
} 
+0

Cela ressemble à quelque chose qui se passe avec le gros tas d'objets ... Pouvez-vous obtenir des compteurs de performance sur sa taille au fil du temps que vous traitez les images? Le LOH ne collecte pas de la même manière que la mémoire standard, et (si je me souviens bien) est beaucoup plus sensible à la fragmentation ... – LorenVS

+1

Cela pourrait bien être un bug dans WPF qui a plusieurs problèmes connus avec de la mémoire non gérée. Je vous recommande de signaler cela à Connect (http://connect.microsoft.com/). Si la fuite de mémoire devient problématique pour votre application et que vous ne pouvez pas obtenir de correctif, vous pouvez déplacer les parties critiques de votre code vers un autre processus ou AppDomain. –

Répondre

3

Résolu le problème.
juste dû remplacer BitmapCacheOption.OnLoad avec BitmapCacheOption.None :)

+2

@Dmitry: Vous devez marquer la réponse comme la réponse acceptée. Si c'était une "réponse" à la réponse de Nymaen, vous devez marquer cette réponse comme la réponse acceptée (cliquez sur le chèque), et l'inclure comme un commentaire, pas comme une réponse séparée ... –

+0

Je ne pouvais pas répondre à la réponse de Nymaen parce que j'ai écrit cela plus tôt qu'il a écrit sa réponse. – mephisto123

2

Je pense qu'il a à faire avec des images - l'objet sous-jacent de l'image classe est non géré, à cause de cette mémoire consommée par les ne sont pas inclus dans les compteurs de GC.

Ils exigent également un soin particulier sur la façon dont vous les jetez - la consommation de mémoire gérée est très faible, donc GC ne fait pas vraiment attention, mais la mémoire non émise - vous pouvez le voir.

La ligne de fond - il ne suffit pas de les laisser hors de portée, vous devez appeler explicitement disposer sur eux lorsque vous avez terminé.

+1

Il n'existe aucune méthode Dispose() dans la classe BitmapImage. – mephisto123

10

Je soupçonne que BitmapCacheOption.OnLoad est ajouter les images à l'empreinte mémoire du cadre, mais puisque vous ne possédez pas les objets dans le cache d'image, ils ne semblent pas dans les résultats de GC appels de méthode. Essayez d'utiliser BitmapCacheOption.None à la place et voir si cela résout vos problèmes de mémoire. Remarque: cela aura un impact considérable sur les performances.

Questions connexes