【问题标题】:Getting an 'out of memory' exception in this relatively simple program在这个相对简单的程序中出现“内存不足”异常
【发布时间】:2010-02-05 14:40:18
【问题描述】:

这是我的 Picture.cs 类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Drawing;

namespace SharpLibrary_MediaManager
{
    public class Picture:BaseFile
    {
        public int Height { get; set; }
        public int Width { get; set; }
        public Image Thumbnail { get; set; }

        /// <summary>
        /// Sets file information of an image from a given image in the file path.
        /// </summary>
        /// <param name="filePath">File path of the image.</param>
        public override void  getFileInformation(string filePath)
        {
            FileInfo fileInformation = new FileInfo(filePath);
            if (fileInformation.Exists)
            {
                Name = fileInformation.Name;
                FileType = fileInformation.Extension;
                Size = fileInformation.Length;
                CreationDate = fileInformation.CreationTime;
                ModificationDate = fileInformation.LastWriteTime;
                Height = calculatePictureHeight(filePath);
                Width = calculatePictureWidth(filePath);                
            }
        }

        public override void getThumbnail(string filePath)
        {            
            Image image = Image.FromFile(filePath);
            Thumbnail = image.GetThumbnailImage(40, 40, null, new IntPtr());            
        }

        private int calculatePictureHeight(string filePath)
        {
            var image = Image.FromFile(filePath);
            return image.Height;
        }

        private int calculatePictureWidth(string filePath)
        {
            var image = Image.FromFile(filePath);
            return image.Width;
        }
    }
}

在这里,我使用该类从给定文件夹中的每个文件中提取信息:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;

namespace SharpLibrary_MediaManager
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        string folderPath = @"D:\Images\PictureFolder";

        private void button1_Click(object sender, EventArgs e)
        {
            DirectoryInfo folder = new DirectoryInfo(folderPath);
            List<Picture> lol = new List<Picture>();
            foreach (FileInfo x in folder.GetFiles())
            {
                Picture picture = new Picture();
                picture.getFileInformation(x.FullName);
                lol.Add(picture);
            }

            MessageBox.Show(lol[0].Name);
        }
    }
}

我遇到了内存不足异常,我真的不知道为什么。这是我第一次做这样的事情,所以我对批处理文件处理等很陌生。

有帮助吗? :)

编辑: 我打开任务管理器查看内存使用情况,当我按下按钮运行该方法时,我注意到我的内存使用量每秒增加 100mb~。

编辑 2: 在我的文件夹中,我有大约 103 张图片,每张图片约为 100kb。 我需要一个解决方案,不管文件夹中有多少图像。有人建议打开图像,施展魔法,然后关闭它。我真的不明白他所说的“关闭”是什么意思。

有人可以推荐一种不同的方法吗? :)

编辑 3: 仍然出现内存不足异常,我已经根据建议更改了 Picture.cs 中的代码,但我没有想法。有什么帮助吗?

public override void  getFileInformation(string filePath)
        {
            FileInfo fileInformation = new FileInfo(filePath);

            using (var image = Image.FromFile(filePath))
            {
                if (fileInformation.Exists)
                {
                    Name = fileInformation.Name;
                    FileType = fileInformation.Extension;
                    Size = fileInformation.Length;
                    CreationDate = fileInformation.CreationTime;
                    ModificationDate = fileInformation.LastWriteTime;
                    Height = image.Height;
                    Width = image.Width;
                }
            }
        }

另外,既然这个问题已经增长了一点,我应该打开一个新问题吗?

【问题讨论】:

  • 你能给我们一个OOM异常的堆栈跟踪吗?
  • 我怎样才能找到它并提供它?
  • 抛出的异常有堆栈跟踪,但没关系,问题已经在您的 calculatePictureHeight 和 calculatePictureWidth 方法中发现。
  • 你能告诉我们你的程序究竟是什么时候崩溃的吗?了解代码中问题发生的确切位置对我们很有帮助。 (别忘了在你的Picture 类上实现IDisposable 接口,因为它包含一个Image 对象。)
  • 您是否考虑过使用 Visual Studio 中的分析器或 CLRProfiler (http://msdn.microsoft.com/en-us/library/ms979205.aspx) 来了解正在分配的对象以及原因?

标签: c# .net image io out-of-memory


【解决方案1】:

您没有在 Image 实例上调用 Dispose。同时创建一次图像,然后提取数据。

另见:

http://msdn.microsoft.com/en-us/library/8th8381z.aspx

编辑 如果复制了您的代码并使用我的图片库对其进行了测试。我的平均FileSize 为每个文件 2-3 MB。我已经执行了你的程序,它确实做了它应该做的事情。 GC 完全符合我的预期。

您的程序的内存始终约为 11-35 MB 私有工作集,提交大小稳定在 43 MB。

在 1156 个总图片大小为 2.9 GB 的文件之后,我中止了该程序。

所以你的内存不足异常肯定还有其他原因。

这是我的程序输出和代码:

1133: Total Size = 2.842,11 MB
1134: Total Size = 2.844,88 MB
1135: Total Size = 2.847,56 MB
1136: Total Size = 2.850,21 MB
1137: Total Size = 2.853,09 MB
1138: Total Size = 2.855,86 MB
1139: Total Size = 2.858,59 MB
1140: Total Size = 2.861,26 MB
1141: Total Size = 2.863,65 MB
1142: Total Size = 2.866,15 MB
1143: Total Size = 2.868,52 MB
1144: Total Size = 2.870,93 MB
1145: Total Size = 2.873,64 MB
1146: Total Size = 2.876,15 MB
1147: Total Size = 2.878,84 MB
1148: Total Size = 2.881,92 MB
1149: Total Size = 2.885,02 MB
1150: Total Size = 2.887,78 MB
1151: Total Size = 2.890,57 MB
1152: Total Size = 2.893,55 MB
1153: Total Size = 2.896,32 MB
1154: Total Size = 2.898,92 MB
1155: Total Size = 2.901,48 MB
1156: Total Size = 2.904,02 MB

源码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Drawing;

namespace SharpLibrary_MediaManager
{
    public abstract class BaseFile
    {
        public string Name { get; set; }
        public string FileType { get; set; }
        public long Size { get; set; }
        public DateTime CreationDate { get; set; }
        public DateTime ModificationDate { get; set; }

        public abstract void getFileInformation(string filePath);

    }


    public class Picture : BaseFile
    {
        public int Height { get; set; }
        public int Width { get; set; }
        public Image Thumbnail { get; set; }

        public override void getFileInformation(string filePath)
        {
            FileInfo fileInformation = new FileInfo(filePath);

            using (var image = Image.FromFile(filePath))
            {
                if (fileInformation.Exists)
                {
                    Name = fileInformation.Name;
                    FileType = fileInformation.Extension;
                    Size = fileInformation.Length;
                    CreationDate = fileInformation.CreationTime;
                    ModificationDate = fileInformation.LastWriteTime;
                    Height = image.Height;
                    Width = image.Width;
                    Thumbnail = image.GetThumbnailImage(40, 40, null, new IntPtr());
                }
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            string folderPath = @"C:\Users\arthur\Pictures";

            DirectoryInfo folder = new DirectoryInfo(folderPath);
            List<Picture> lol = new List<Picture>();
            double totalFileSize = 0;
            int counter = 0;
            foreach (FileInfo x in folder.GetFiles("*.jpg", SearchOption.AllDirectories))
            {
                Picture p = new Picture();
                p.getFileInformation(x.FullName);
                lol.Add(p);
                totalFileSize += p.Size;
                Console.WriteLine("{0}: Total Size = {1:n2} MB", ++counter, totalFileSize / 1048576.0);
            }

            foreach (var p in lol)
            {
                Console.WriteLine("{0}: {1}x{2} px", p.Name, p.Width, p.Height);
            }
        }
    }
}

【讨论】:

    【解决方案2】:

    您必须释放打开Image 对象时使用的资源。

    您可以调用 Dispose,或在 Using 语句中创建图像

    例如

    public override void getThumbnail(string filePath)
    {
        using (Image image = Image.FromFile(filePath))
        {
            Thumbnail = image.GetThumbnailImage(40, 40, null, new IntPtr());
        }
    }
    

    由于您的类包含Image,您应该实现IDisposable 接口,因此您也可以在using 语句中使用它。

    【讨论】:

    • 我没有看到using 语句被经常使用。只是不为人所知吗?
    • 我可以在任何地方使用它(即当目标类是IDisposable,并且我的程序流程允许它时)。你不必玩Dispose,而且你确信你的内存管理是正确的。
    • 这为我修好了。
    【解决方案3】:

    我看到了一些问题。首先,您在随后调用CalculatePictureWidth 和CalculatePictureHeight 时加载每个图像两次。其次,你实际上从来没有对缩略图做任何事情,它看起来。第三,从 Image 实例收集信息后,您应该对它们调用 Dispose。

    【讨论】:

    • 嗯,没有看到Out Of Memory异常的StackTrace(当异常闯入IDE时,应该可以点击CallStack窗口(Debug->Windows->CallStack),我注意到的唯一另一件事是您正在获取 FileInfo,循环遍历每个文件,将名称传递给另一个函数,然后再次获取相同的 FileInfo。这可以简化,但我不认为它会导致您的问题。跨度>
    【解决方案4】:

    图像文件包含一个大块非托管数据的句柄。必须删除此数据,您可以通过显式调用 Image.Dispose 来执行此操作。因为您没有在 calculatePictureHeight 和 calculatePictureWidth 方法中调用 Dispose,所以 Image 文件保存在内存中,这会导致您的 OOM 异常。

    [更新]: 也许我多一点背景是有用的,为什么这实际上会发生。毕竟,难道没有 GC 来为我们收拾这个烂摊子吗?是的,而且 GC 非常有效。它最终还会清理我们必须处理的对象。

    Image 类包含一个终结器。此终结器将确保清除所有资源,即使您忘记调用 Dispose。但是,这里发生的情况是您的内存不足,这会立即触发 GC.Collect。在此收集期间,GC 会找到您所有未引用的图像。但是,因为 GC 不会立即运行终结器方法,所以您的所有对象都被提升为 Gen1(它们保存在内存中),您的所有资源(本机内存)也是如此。因此,即使在 GC 启动后,内存仍然太少,您会遇到 OOM 异常。

    这就是为什么您应该始终处置实现 IDisposable 的对象。

    【讨论】:

      【解决方案5】:

      您不应该将所有图像存储在一个列表中,您应该一次处理一张图片,并在每次迭代后处理每张图片。

      检查 Image.Dispose() 方法。

      【讨论】:

      • 如何处理实例化类?
      • 您可以使用 using 语句,例如 using(Picture p = new Picture()) { //do work here } 然后在括号末尾调用 dispose 方法,或者您可以简单调用 Picture 的 Dispose() 方法。
      【解决方案6】:

      您在该文件夹中有多少张图片。我看到您循环浏览所有图片并将它们直接加载到内存中并将它们存储在列表中。这可能是原因。

      【讨论】:

      • 看起来他想要做的至少是循环遍历它们,获取高度、宽度和缩略图,然后存储这些。因此,每张实际图片应该只在计算时才在内存中。
      【解决方案7】:

      尝试一次查看一张图片。我发现了某些 .jpg 的原因——图像处理库对某些图像有问题,不知道为什么。但是我们已经从 Photoshop 中以某种奇怪的方式保存了某些 .jpg 文件。我敢打赌你会发现这是一个坏苹果。

      【讨论】:

        【解决方案8】:

        您的图片文件有多大?您的目录中有多少文件? 根据这些参数,我可以看到如何获得 OutOfMemory。您可能需要重新检查您的方法。与其将所有内容都加载到内存中,不如单独加载每张图片,执行您的操作,然后继续(当然,在您处理掉上一张图片之后)。

        【讨论】:

          【解决方案9】:

          你能做到吗?

          public class Picture:BaseFile
          {
              public int Height { get; set; }
              public int Width { get; set; }
              public Image Thumbnail { get; set; }
          
              /// <summary>
              /// Sets file information of an image from a given image in the file path.
              /// </summary>
              /// <param name="filePath">File path of the image.</param>
              public override void  getFileInformation(string filePath)
              {
                  FileInfo fileInformation = new FileInfo(filePath);
                  if (fileInformation.Exists)
                  {
                      /*
                      Name, FileType, Size, etc
                      */
                      using (Image image = Image.FromFile(filePath))
                      {
                          Height = image.Height;
                          Width = image.Width;
                          Thumbnail = image.GetThumbnailImage(40, 40, new Image.GetThumbnailImageAbort(ThumbnailCallback), default(IntPtr));
                      }
                  }
              }
          
              public bool ThumbnailCallback()
              {
                  return false;
              }
          }
          

          【讨论】:

            【解决方案10】:

            我认为问题是:

            如果调用 GetThumbnailImage 时将宽度或高度参数之一设为 -1,它也会抛出 OutOfMemoryException。

            如果您通过使用 Image.FromStream(例如 MemoryStream)创建一个 Image 对象,然后关闭该流(MSDN 明确表示在对象的生命周期内不要这样做),然后调用此方法 (GetThumbnailImage)对象将抛出 OutOfMemoryException。

            【讨论】:

              【解决方案11】:

              一些人提到在处理完图像后调用 dispose。然而,这还不够,随着内存使用量的增加,.net 框架将为您做一些事情。由于您已将每张图片添加到链接列表中,因此 .NET 框架无法处理它,因为您隐含地保留了引用。

              这意味着存储在图片类中的所有数据都在 foreach 循环期间保留。从代码片段中我看不出它是什么,它可能包含压缩甚至未压缩的图像版本,这可以解释你的内存爆炸。

              [编辑] 删除了其他人指出无关的部分答案;我假设图片类是 XNA 图片类。

              【讨论】:

              • 我没有使用 XNA,我的类只是叫做 Picture。
              • 我在谈到处理它们时所指的图片是实际图片,而不是缩略图。所以我认为(但我一定是错的)没有保留参考,因此处置它们就足够了。唯一会保留的是缩略图,这就是为什么我建议他在他的Picture 类中实现IDisposable
              • 框架会在垃圾收集时调用 finalize,但是他保留了对图像的引用,因此永远不会被收集
              • 你说的是哪张图片?
              • 公开图片缩略图{get;放; } 在图片类中
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2015-10-27
              • 1970-01-01
              • 2010-10-10
              • 1970-01-01
              相关资源
              最近更新 更多