【问题标题】:Loading many large photos into a Panel efficiently有效地将许多大照片加载到面板中
【发布时间】:2014-05-06 15:09:32
【问题描述】:

如何从一个目录及其子目录中加载许多大照片以防止出现 OutOfMemoryException?

我一直在使用:

foreach(string file in files)
{
    PictureBox pic = new PictureBox() { Image = Image.FromFile(file) };
    this.Controls.Add(pic);
}

到目前为止一直有效。我现在需要处理的照片大小在 15 到 40MB 之间,而且可能有数百张。

【问题讨论】:

  • 理想情况下,我希望能够遍历文件/照片列表以将每个文件/照片加载并显示到面板控件上。而且我不希望表单在这样做时变得无响应。
  • 是的,当它们显示时,它们应该是全分辨率的。
  • 面板有多大?如果内存是一个问题,我认为在一个小窗口中显示一个巨大的图像而不先缩小它是没有意义的。
  • @Jaycee 谢谢,我正在看。

标签: c# .net winforms


【解决方案1】:

您正在使用这种方法攻击垃圾收集器。在循环中加载 15-40mb 的对象总是会引发 OutOfMemoryException。这是因为对象直接进入大对象堆,所有大于 85K 的对象都这样做。大对象立即成为第 2 代对象,并且从 .Net 4.5.1(您请求它)开始,内存不会自动压缩,并且在早期版本中根本不会被压缩。

因此,即使您最初加载对象并且应用程序继续运行,这些对象也很有可能会挂起,即使完全取消引用,也会将大对象堆碎片化。一旦发生碎片,例如用户关闭控件一两分钟做其他事情并再次打开控件,很可能所有新对象都无法插入 LOH - 分配发生时内存必须是连续的。出于性能原因,GC 在 Gen 2 和 LOH 上运行收集的频率要低得多 - memcpy 由 GC 在后台使用,这在较大的内存块上很昂贵。

此外,如果您从正在使用的控件引用所有这些图像,则不会释放所消耗的内存,想象一下选项卡。这样做的整个想法是错误的。根据用户需要使用缩略图或加载全尺寸图像,并注意消耗的内存。

更新 与其告诉你应该做什么和不应该做什么,我决定尝试帮助你做到这一点:)

我编写了一个小程序,它在一个包含 440 个 jpeg 文件的目录上运行,总大小为 335 兆字节。当我第一次运行您的代码时,我得到了 OutOfMemoryException 并且表单仍然没有响应。

第 1 步 首先要注意的是,如果您要编译为 x86 或 AnyCpu,则需要将其更改为 x64。右键单击项目,转到构建选项卡并将目标平台设置为 x64。

这是因为在 32 位 x86 平台上可以寻址的内存量是有限的。所有 .Net 进程都在虚拟地址空间内运行,CLR 堆大小将是操作系统允许的任何进程,并且实际上不在开发人员的控制范围内。但是,它将分配尽可能多的内存 - 我在 64 位 Windows 8.1 上运行,因此更改目标平台为我提供了几乎无限量的内存空间可供使用 - 直到物理内存的限制,您的进程将被允许.

执行此操作后,您的代码不会导致 OutOfMemoryException

第 2 步 我将目标框架从 VS 2013 中的默认 4.5 更改为 4.5.1。我这样做是为了可以使用 GCSettings.LargeObjectHeapCompactionMode,因为它仅在 4.5.1 中可用。我注意到关闭表单需要很长时间,因为 GC 正在做大量的工作来释放内存。基本上我会在 loadPics 代码的末尾设置它,因为它允许大对象堆在下一次阻塞垃圾收集时不会碎片化。我相信这对您的应用程序至关重要,因此如果可能,请尝试使用此版本的框架。您也应该在早期版本上对其进行测试,以查看与您的应用交互时的差异。

第 3 步 由于应用仍然没有响应,我让代码异步运行

第 4 步 由于代码现在在与 UI 线程不同的线程上运行,因此在访问表单时会导致 GUI 跨线程异常,因此我不得不使用 Invoke 将消息从代码的线程发回 UI 线程。这是因为 UI 控件只能从 UI 线程访问。

代码

private async void button1_Click(object sender, EventArgs e)
{
    await LoadAllPics();
}

private async Task LoadAllPics()
{
    IEnumerable<string> files = Directory.EnumerateFiles(@"C:\Dropbox\Photos", "*.JPG", SearchOption.AllDirectories);
    await Task.Run(() =>
    {
        foreach(string file in files)
        {  
            Invoke((MethodInvoker)(() => 
            {
                PictureBox pic = new PictureBox() { Image = Image.FromFile(file) };
                this.Controls.Add(pic);
            }));
        }
    }
    );
    GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
}

【讨论】:

  • -1 有两个原因: 1 - 您没有提供解决方案,而是描述了问题的根本原因。 2 - The whole idea of doing this is misconceived - 除了我正在使用相关的 .Net 技术进行 here 之外,它工作得非常好,即使我没有虚拟化图像数据。
  • @HighCore 如果你这样做,你的答案在哪里?
  • @HighCore 我的理解是创建大型瞬态对象是错误的做法,请参阅msdn.microsoft.com/en-us/library/…。除非您知道得更多,否则解决方案是不要这样做
  • +1。很酷,虽然我的更酷 XD .. 你也可以删除我回答中的反对票吗?
【解决方案2】:

您可以在放置 UI 时尝试调整图像大小。

foreach(string file in files)
{
    PictureBox pic = new PictureBox() { Image = Image.FromFile(file).resizeImage(50,50) };
    this.Controls.Add(pic);
}

public static Image resizeImage(this Image imgToResize, Size size)
{
   return (Image)(new Bitmap(imgToResize, size));
}

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2020-08-11
  • 1970-01-01
  • 2023-04-06
  • 2011-12-22
  • 2015-11-26
  • 1970-01-01
  • 2019-03-16
  • 2014-04-06
相关资源
最近更新 更多