3 Stimmen

C# WPF-Anwendung verbraucht zu viel Speicher, während GC.GetTotalMemory() niedrig ist

Ich habe eine kleine WPF-Anwendung mit 2 Threads geschrieben - der Hauptthread ist der GUI-Thread und ein weiterer Thread ist der Worker.
App hat ein WPF-Formular mit einigen Steuerelementen. Es gibt eine Schaltfläche, mit der man ein Verzeichnis auswählen kann. Nach der Auswahl des Verzeichnisses, Anwendung scannt für .jpg-Dateien in diesem Verzeichnis und prüft, ob ihre Thumbnails in hashtable sind. wenn sie sind, es tut nichts. sonst ist es ihre vollen Dateinamen in die Warteschlange für Arbeiter hinzufügen.
Worker nimmt Dateinamen aus dieser Warteschlange, lädt JPEG-Bilder (unter Verwendung von WPF's JpegBitmapDecoder und BitmapFrame), erstellt Miniaturbilder von ihnen (unter Verwendung von WPF's TransformedBitmap) und fügt sie der Hashtabelle hinzu.
Alles funktioniert einwandfrei, außer dem Speicherverbrauch dieser Anwendung bei der Erstellung von Miniaturansichten für große Bilder (z. B. 5000x5000 Pixel). Ich habe Textfelder auf meinem Formular hinzugefügt, um den Speicherverbrauch zu zeigen (GC.GetTotalMemory() und Process.GetCurrentProcess().PrivateMemorySize64) und war sehr überrascht, denn GC.GetTotalMemory() bleibt in der Nähe von 1-2 MByte, während die Größe des privaten Speichers ständig wächst, vor allem beim Laden neuer Bilder (~ +100 MB pro Bild).
Selbst nach dem Laden aller Bilder, dem Erstellen von Thumbnails und dem Freigeben der Originalbilder bleibt die Größe des privaten Speichers bei etwa 700-800 MByte. Meine VirtualBox ist auf 512 MB physischen Speicher beschränkt, und Windows in VirtualBox fängt an, viel zu swappen, um diesen enormen Speicherverbrauch zu bewältigen. Ich vermute, dass ich etwas falsch mache, aber ich weiß nicht, wie ich dieses Problem untersuchen soll, denn laut GC ist die Größe des zugewiesenen Speichers sehr gering.

Anhängen des Codes der Thumbnail-Loader-Klasse:

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

10voto

Nymaen Punkte 101

Ich vermute, dass BitmapCacheOption.OnLoad fügt die Bilder dem Speicherbedarf des Frameworks hinzu, aber da Sie die Objekte im Bild-Cache nicht besitzen, erscheinen sie nicht in den Ergebnissen von GC Methodenaufrufe. Versuchen Sie es mit BitmapCacheOption.None und sehen Sie, ob das Ihre Speicherprobleme löst. Hinweis: Dies hat einen drastischen Einfluss auf die Leistung.

3voto

mephisto123 Punkte 1290

Das Problem wurde gelöst.
Ich musste nur BitmapCacheOption.OnLoad durch BitmapCacheOption.None ersetzen :)

2voto

mfeingold Punkte 6996

Ich denke, es hat mit Bildern zu tun - das zugrundeliegende Objekt der Image-Klasse ist nicht verwaltet, daher wird der von ihnen verbrauchte Speicher nicht in die GC-Zähler aufgenommen.

Sie erfordern auch besondere Sorgfalt, wie Sie sie entsorgen - verwalteten Speicherverbrauch von ihnen ist sehr gering, so GC nicht wirklich Aufmerksamkeit, aber die unveränderten Speicher - Sie können es sehen.

Unterm Strich reicht es nicht aus, sie aus dem Anwendungsbereich herauszulassen, man muss sie explizit dispose nennen, wenn man fertig ist.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X