C# WPF 应用程序使用过多内存,而 GC.GetTotalMemory() 较低

标签 c# wpf memory-leaks

我用 2 个线程编写了小 WPF 应用程序 - 主线程是 GUI 线程,另一个线程是工作线程。
应用程序有一个带有一些控件的 WPF 表单。有一个按钮,允许选择目录。 选择目录后,应用程序会扫描该目录中的 .jpg 文件并检查它们的缩略图是否在哈希表中。如果是,它什么都不做。否则它会将他们的完整文件名添加到工作队列中。
工作人员正在从此队列中获取文件名,加载 JPEG 图像(使用 WPF 的 JpegBitmapDecoder 和 BitmapFrame),制作它们的缩略图(使用 WPF 的 TransformedBitmap)并将它们添加到哈希表中。
一切正常,除了在为大图像(如 5000x5000 像素)制作缩略图时此应用程序的内存消耗。我在我的表单上添加了文本框以显示内存消耗(GC.GetTotalMemory() 和 Process.GetCurrentProcess().PrivateMemorySize64)并且非常惊讶,因为 GC.GetTotalMemory() 保持接近 1-2 MB,而私有(private)内存大小不断增长,尤其是在加载新图像时(每张图像约 +100Mb)。
即使在加载所有图像、制作缩略图并释放原始图像之后,私有(private)内存大小仍保持在 ~700-800Mbytes。我的 VirtualBox 被限制为 512Mb 的物理内存,并且 VirtualBox 中的 Windows 开始大量交换以处理这种巨大的内存消耗。我想我做错了什么,但我不知道如何调查这个问题,因为根据 GC,分配的内存大小非常低。

附加缩略图加载器类的代码:

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

最佳答案

我怀疑 BitmapCacheOption.OnLoad 将图像添加到框架的内存占用中,但由于您不拥有图像缓存中的对象,因此它们不会出现在 的结果中GC 方法调用。尝试改用 BitmapCacheOption.None,看看是否能解决您的内存问题。注意:这样做会对性能产生巨大影响。

关于C# WPF 应用程序使用过多内存,而 GC.GetTotalMemory() 较低,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2557278/

相关文章:

ios - 使用 __bridge 转换 ABPersonCopyImageDataWithFormat 的结果以创建 UIImage 时如何防止对象的潜在泄漏?

jsp - tomcat - 计时器内存泄漏

c# - 了解 Build C++

c# - MongoDB - 更新字段或添加字段(如果 BsonDocument C# 上不存在)

c# - XPath 选择内文

c# - 如何让 Omnisharp 扩展在 Visual Studio Code 中工作

c# - 将表达式<Func<T1>> 转换为表达式<Func<T1, T2>>

wpf - 如何使用过滤器集自动刷新 ListCollectionView

c# - 以线程安全的方式创建新窗口

c++ - 对 MFC 项目中的内存泄漏感到困惑,如果从未调用 _CrtDumpMemoryLeaks(),这些内存泄漏就会消失