为什么位图会留在内存中,除非我调用GC.Collect

本文关键字:非我 调用 GC Collect 位图 内存 为什么 | 更新日期: 2023-09-27 18:10:58

我正在开发一个连接到GigEVision相机的应用程序,并从中提取图像。我目前正在使用Pleora eBus SDK与c# . net。

下面的代码只是一个相机连接的测试应用程序-它可以流图像,但很快耗尽内存,除非我调用GC.Collect();值得注意的是,正在流式传输的图像很大(4096x3072),因此崩溃发生得相当快。

一开始我怀疑没有调用Dispose()是问题所在。但是,我可以在删除对每个图像的引用之前对其调用Dispose(),这并没有解决问题。

我也试过显式地释放进入显示线程回调的缓冲区,但没有效果。

我可以用一种更优雅的方式找回我的记忆吗?

using System;
using System.Windows.Forms;
using PvDotNet;
using PvGUIDotNet;
using System.Drawing;
namespace eBus_Connection
{
    public partial class MainForm : Form
    {
        PvDeviceGEV camera;
        PvStreamGEV stream;
        PvPipeline pipeline;
        PvDisplayThread thread;
        bool updating = false;
        public MainForm()
        {
            InitializeComponent();
        }
        private void MainForm_Shown(object sender, EventArgs e)
        {
            PvDeviceInfo info;
            PvDeviceFinderForm form = new PvDeviceFinderForm();
            form.ShowDialog();
            info = form.Selected;
            camera = PvDeviceGEV.CreateAndConnect(info) as PvDeviceGEV;
            stream = PvStreamGEV.CreateAndOpen(info.ConnectionID) as PvStreamGEV;
            pipeline = new PvPipeline(stream);
            if (camera == null || stream == null)
                throw new Exception("Camera or stream could not be created.");
            camera.NegotiatePacketSize();
            camera.SetStreamDestination(stream.LocalIPAddress, stream.LocalPort);
            camera.StreamEnable();
            camera.Parameters.ExecuteCommand("AcquisitionStart");
            pipeline.Start();
            thread = new PvDisplayThread();
            thread.OnBufferDisplay += thread_OnBufferDisplay;
            thread.Start(pipeline, camera.Parameters);
            status.DisplayThread = thread;
            status.Stream = stream;
        }
        void thread_OnBufferDisplay(PvDisplayThread aDisplayThread, PvBuffer aBuffer)
        {
            Bitmap b = new Bitmap((int)aBuffer.Image.Width, (int)aBuffer.Image.Height);
            aBuffer.Image.CopyToBitmap(b);
            BeginInvoke(new Action<Bitmap>(ChangeImage), b);
        }
        void ChangeImage(Bitmap b)
        {
            if (PictureBox.Image != null)
                PictureBox.Dispose();
            PictureBox.Image = b;
            GC.Collect();//taking this away causes memory to leak rapidly.
        }
    }
}

为什么位图会留在内存中,除非我调用GC.Collect

很可能在代码中的某个地方没有处理Image,例如BitmapBitmap扩展Image,实现IDisposable,这意味着你需要调用Dispose(),当你完成它(通常通过包装它与using语句)。您没有将BitmapImage丢弃在某个地方,以便GC在可能的时候结束它(或者在这种情况下,当您显式调用GC时)。

一旦GC确定一个类不再被引用,它就可以被清理…在清理之前,它检查终结器。如果存在终结器,则该类将被放置在一个特殊的GC终结器队列中,该队列将在清理资源/内存之前运行该终结器。大多数IDisposable类都有终结器,在您忘记自己手动处置类的情况下,允许GC执行Dispose()调用工作。这似乎是发生在你的代码,但没有看到所有的类,我只能猜测什么是不处置(不知道在哪里)。

编辑:我确实有一个猜测。我打赌PictureBox.Dispose()调用不会处理PictureBox.Image

如果一个对象实现了IDisposable,那么你绝对应该在它上面调用Dispose,但是释放一个对象并不会释放它占用的内存。它释放一些东西,比如,在这个例子中,一个图像句柄。在回收内存之前,必须先释放这些资源,因此释放这些资源仍然有帮助。

当GC运行时,如果一个对象还没有被处理,那么它必须首先完成它,这意味着它必须等待更长的时间来回收内存。如果对象已被处理,则在GC运行时立即回收内存。

GC在后台运行。如果您的应用程序忙于分配越来越多的内存,那么无论您是否处置对象,GC都永远没有机会运行和回收内存。在这种情况下,您需要不时显式地调用GC。创建多个映像是最常见的需要显式GC调用的场景。

值得注意的是,所有对象都保留在内存中,直到GC运行并清理它们,无论对象是否实现了IDisposable。你通常不会注意到它,因为大多数应用程序有足够的停机时间来允许GC隐式运行并回收内存。在这方面,Bitmap对象没有什么特别之处。

您正在处理图片框而不是图像。即使这将处理图片框中的图像,它也只会在第一次这样做。之后,图片框处于已处理状态,再次调用Dispose将不做任何事情。

你应该从图片框中获取图像参考,并在不再使用时处理它:

void ChangeImage(Bitmap b) {
  Image oldImage = PictureBox.Image;
  PictureBox.Image = b;
  if (oldImage != null) {
    oldImage.Dispose();
  }
}

未正确处理的位图必须在收集之前完成。有一个后台线程完成需要收集的对象,但是如果你放弃对象的速度比该线程处理它们的速度快,你就会耗尽内存。

当位图被正确处理后,它就变成了一个常规的托管对象,可以在垃圾收集器想要的时候立即收集。