有效地将许多大照片加载到面板中

本文关键字:加载 许多大 照片 有效地 | 更新日期: 2023-09-27 18:01:52

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

我一直在使用:

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

直到现在还在工作。我现在需要处理的照片大小在15到40MB之间,可能有数百张。

有效地将许多大照片加载到面板中

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

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

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

比起告诉你该做什么不该做什么,我决定试着帮你做这件事:)

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

步骤1

首先要注意的是,如果您正在编译为x86或AnyCpu,则需要将其更改为x64。右键单击project,转到Build选项卡并将目标平台设置为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;
}

你可以试着调整图片的大小,当你把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));
}