并行调整图像大小导致内存不足异常

Posted

技术标签:

【中文标题】并行调整图像大小导致内存不足异常【英文标题】:Resizing images in parallel causing out of memory exception 【发布时间】:2017-08-23 18:01:11 【问题描述】:

我正在将大量图像并行调整为 1000x1000 缩略图,并且很快就会耗尽内存。 (性能分析器在大约 3 分钟后将我的已用内存设为 3GB)

最初我使用Image.FromFile(),但做了一些研究,我发现Image.FromStream() 是要走的路。我认为我有适当的 using 语句,某处的东西仍在内存中,而 GC 没有按预期清除资源。

似乎 GDI+ 保持手柄打开存在问题,但我似乎找不到适合我的情况的解决方案。

问题:

    我做错了什么吗? 如果没有,有没有更好的方法来Dispose() 的流/图像/ResizedImage,这样我就不会耗尽所有资源,同时仍然保持快速的并行操作? 如果 GDI+ 是问题所在并且使非托管资源保持活动状态,我该如何解决该问题?

代码

List<FileInfo> files 包含 ~300 张有效的 JPG 图片,每个 JPG ~2-4mb

来电者

    public void Execute()
    
        Parallel.ForEach(Files, (file) =>
        
            Resize.ResizeImage(file.FullName);
        
        );
    

Execute() 调用 Parallel.Foreach()..

调整类的大小

public static class Resize

    public static void ResizeImage(string fileName)
    
        ResizeImage(fileName, 1000, 1000, true);
    
            
    public static void ResizeImage(string fileName, int newHeight, int newWidth, bool keepAspectRatio = true)
    
        string saveto = Path.GetDirectoryName(fileName) + @"\Alternate\" + Path.GetFileName(fileName);
        try
        
            using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
            
                using (Image ImageFromStream = Image.FromStream(fs))
                
                    var ImageSize = new Size(newHeight, newWidth);
                    if (keepAspectRatio)
                    
                        int oWidth = ImageFromStream.Width;
                        int oHeight = ImageFromStream.Height;
                        double pWidth = ((double)ImageSize.Width / (double)oWidth);
                        double pHeight = ((double)ImageSize.Height / (double)oWidth);
                        double percent;
                        if (pHeight < pWidth)
                            percent = pHeight;
                        else
                            percent = pWidth;
                        newWidth = (int)(oWidth * percent);
                        newHeight = (int)(oHeight * percent);
                    
                    else
                    
                        newWidth = ImageSize.Width;
                        newHeight = ImageSize.Height;
                    
                    var ResizedImage = new Bitmap(newWidth, newHeight);
                    using (Graphics gfxHandle = Graphics.FromImage(ResizedImage))
                    
                        gfxHandle.InterpolationMode = InterpolationMode.HighQualityBicubic;
                        gfxHandle.DrawImage(ImageFromStream, 0, 0, newWidth, newHeight);
                        if (!Directory.Exists(Path.GetDirectoryName(saveto)))  Directory.CreateDirectory(Path.GetDirectoryName(saveto)); 
                        ResizedImage.Save(saveto, ImageFormat.Jpeg);
                    
                    ResizedImage.Dispose();
                    ResizedImage = null;
                
            
        
        catch (Exception ex)
        
            Debug.WriteLine(string.Format("Exception: 0", ex.Message));
        
    

【问题讨论】:

我确实记得听说过 GDI+ 没有正确释放资源,但那是不久前的事了,所以我对细节很粗略。您可以做的是使用GC.Collect() 强制垃圾收集循环,或者使用显式工作线程限制并行调整大小的数量。或者甚至两者兼而有之。 @ZacFaragher A GC.Collect() 在这种情况下不会做任何事情。我在看性能分析器,垃圾人来收拾东西,但它不在路边。不过,您对并行调整大小的数量是正确的,您刚刚向我指出了解决方案,谢谢。 【参考方案1】:

This explanation of parallelism 指出我的Parallel.ForEach() 基本上是在创建过多的新任务,因为它正在等待磁盘访问。在大约 5 分钟标记处,大约在引发异常时,大约有 160 个线程。降低并行度会限制创建的线程数量,以及在内存中等待完成加载或写入磁盘之前超出范围并被处理掉的图像数量。设置MaxDegreeOfParallelism = 2 似乎是网络磁盘访问的最佳选择,并将我的线程数减少到 25 个左右,并将 CPU 利用率提高到约 35%(从 17-24% 上升,由于 GC 阻塞线程和 CPU 开销)许多线程)

    public void Execute()
    
        //Parallel.ForEach(Files, (file) =>
        //
        //    Resize.ResizeImage(file.FullName);
        //
        //);

        Parallel.ForEach(Files, new ParallelOptions()  MaxDegreeOfParallelism = 2 , (file) =>  Resize.ResizeImage(file.FullName);  );

    

感谢@ZacFaragher。

【讨论】:

很好,干得好!而今天我得知Parallel.ForEach() 存在!

以上是关于并行调整图像大小导致内存不足异常的主要内容,如果未能解决你的问题,请参考以下文章

异步上传和调整多个图像大小时出现内存不足异常

虚拟内存是啥东西?有啥用?如果虚拟内存不足会有啥危害?如何调整虚拟内存的大小?

电脑开机一会就出现内存不足,是啥原因

保留内存是不是会导致内存不足异常

在android上调整位图大小的最节省内存的方法?

BitmapFactory.decodeFile 上的内存不足