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

Resizing images in parallel causing out of memory exception

我正在将大量图像并行调整为 1000x1000 缩略图,运行 内存很快就用完了。 (性能分析器让我在大约 3 分钟后使用了 3GB 的内存)

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

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

问题:

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

代码

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

来电者

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

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

调整大小Class

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

This explanation 的并行性指出我的 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。