System.Drawing.Image.FromStream()上的Out of Memory异常

时间:2015-07-10 08:44:56

标签: c# image image-processing out-of-memory

我有一个应用程序,它会在长时间的迭代过程中处理和重新调整图像大小并且偶尔会出现OutOfMemoryException。

我将我的图像作为文件流存储在数据库中,在处理过程中我需要将它们保存到临时物理位置。

我的模特:

[Table("Car")]
public class Car
{
   [... some fields ...]
   public virtual ICollection<CarPhoto> CarPhotos { get; set; }
}

[Table("CarPhoto")]
public class CarPhoto
{
   [... some fields ...]
   public Guid Key { get; set; }

   [Column(TypeName = "image")]
   public byte[] Binary { get; set; }
}

处理看起来大致如下:

foreach (var car in cars)
{
    foreach (var photo in car.CarPhotos)
    {
        using (var memoryStream = new MemoryStream(photo.Binary))
        {
            using (var image = Image.FromStream(memoryStream)) // this is where the exception is thrown
            {
                var ratioX = 600.00 / image.Width;

                var newWidth = (int)(image.Width * ratioX);
                var newHeight = (int)(image.Height * ratioX);

                using (var bitmap = new Bitmap(newWidth, newHeight))
                {
                    Graphics.FromImage(bitmap).DrawImage(image, 0, 0, newWidth, newHeight);
                    bitmap.Save(directory + filePath);
                }
            }
        }
    }
}

我查看了this类似的帖子,但似乎没有一个答案适用于我的情况。

其中一个答案建议使用Image.FromStream(),但它仍然是我正在做的事情。

我非常有信心我的所有图片都有效。异常似乎是随机发生的,最常见的是处理较大的文件,但也适用于较小的文件。有时一个图像会失败,但下次会被处理好。

据我所见,我正确处理了所有事情(内存流,图像和位图)。

这项工作正在受到Hangfire的影响。这可能会导致问题吗?

3 个答案:

答案 0 :(得分:6)

你的程序吃了很多的内存,获得OOM肯定不会出乎意料。它究竟会死在哪里是不可预测的。但是,是的,创建位图是它可能首先死亡的地方。按顺序解决最可能的原因:

   foreach (var car in cars)

您处理的汽车的数量没有明显的上限。每辆车都有多个图像,您似乎将这些图像存储在内存中(photos.Binary)。或者换句话说,这个程序保证迟早会死,只是因为它需要处理越来越多的汽车。获得成功的唯一方法是通过串行处理汽车而不是将它们全部存储在内存中。可能是不愉快的建议,强烈建议在64位模式下运行此代码。

   using (var memoryStream = new MemoryStream(photo.Binary))

该内存流是一个大问题,它的底层缓冲区很可能存储在Large Object Heap中。 LOH没有被压缩,重复分配MemoryStream会使这个堆可能碎片化。迟早你会跑出一个足够大的洞,以适应下一张照片的缓冲区。超出随机,确切地说,何时发生取决于您之前处理的照片类型。您将要重新使用该对象,而不是一遍又一遍地重新分配它。只需在进入循环之前创建一次,将容量设置为一个很好的大数字。

    Graphics.FromImage(bitmap).DrawImage(image, 0, 0, newWidth, newHeight);

需要处理图形对象。使用 using 语句就像使用其他对象一样。

总的来说,你的程序的真正问题在于它根本无法扩展,并且在需要处理不断增加的数据集时总是会出现问题。修复这可能是一个非常重要的重写,你几乎肯定想要在其上翻转忽略位并利用64位进程中的可用地址空间。硬件救援,今天很容易获得。

答案 1 :(得分:0)

只需使用:

即可避免执行所有Bitmap代码(可能会解决内存问题)
var resized = image.GetThumbnailImage(newWidth, newHeight,() => false, IntPtr.Zero);
resized.Save(directory + filePath);

答案 2 :(得分:0)

我遇到了同样的问题。问题是即使你使用USING语句,Image Object也不会立即处理。例如

 IEnumerable<Control> mcontrols = this.panel1.Controls.Cast<Control>().Where(n => n.GetType() == typeof(PreviewImage));
        // for unclear reason after the dispose needs many times to clear everything;
        while (mcontrols.Count() != 0)
        {
            foreach (PreviewImage pi in mcontrols)
            {
                pi.Dispose();
            }
            mcontrols = this.panel1.Controls.Cast<Control>().Where(n => n.GetType() == typeof(PreviewImage));
            if (mcontrols == null) {
                break;
            }

        }

即使在第一个循环仍然在我的panel1中我没有废弃控件。是的,它是可怕的解决方法,但它适用于我