将许多大型照片有效地加载到Panel中

时间:2014-05-06 15:09:32

标签: c# .net winforms

如何从目录及其子目录中加载许多大型照片,以防止OutOfMemoryException?

我一直在使用:

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

到目前为止一直有效。我现在需要使用的照片每张都在15到40MB之间,可能有数百张。

2 个答案:

答案 0 :(得分:4)

你用这种方法攻击垃圾收集器。在循环中加载15-40mb对象将始终邀请OutOfMemoryException。这是因为对象直接进入大对象堆,所有对象> 85K呢。大型对象立即成为Gen 2对象,并且内存不会自.Net 4.5.1(您请求它)自动压缩,并且在早期版本中根本不会被压缩。

因此,即使您最初加载对象并且应用程序继续运行,也很有可能这些对象即使在完全取消引用时也会挂起,从而破坏大对象堆。一旦碎片发生,例如用户关闭控件以执行其他操作一两分钟并再次打开控件,则更有可能所有新对象都无法插入到LOH中 - < em>分配发生时内存必须是连续的 。出于性能原因,GC在Gen 2和LOH上运行集合的次数要少得多 - 后台GC使用memcpy,这在较大的内存块上很昂贵。

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

<强>更新 而不是告诉你你应该和不应该做什么我决定尝试帮助你做到这一点:)

我写了一个小程序,它运行在一个包含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)

您可以在使用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));
}