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

时间:2014-07-01 00:01:48

标签: c# .net bitmap

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

下面的代码只是相机连接的测试应用程序 - 它可以流式传输图像,但除非我调用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.
        }
    }
}

3 个答案:

答案 0 :(得分:1)

您的代码中某处Image(例如Bitmap)很可能没​​有被处理掉。 Bitmap扩展了Image,它实现了IDisposable,这意味着当你完成它时需要在其上调用Dispose()(通常用{{1}包裹它声明)。您不会在某处处置usingBitmap,以便GC在可能的情况下(或者在您明确调用GC时)最终确定它。

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

编辑:我确实猜到了。我打赌Dispose()来电不会处置PictureBox.Dispose()

答案 1 :(得分:0)

如果一个对象实现IDisposable,那么你应该绝对调用Dispose,但是处置一个对象并不会释放它占用的内存。它会释放像这样的图像句柄。在回收内存之前,必须首先释放这些资源,因此处理仍然有帮助。

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

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

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

答案 2 :(得分:0)

您正在处理图片框而不是图像。即使它将图像处理在图片框中,它也只会在第一次这样做。之后,图片框处于已处理状态,再次呼叫Dispose将无效。

您应该从图片框中获取图像参考,并在它不再使用后进行处理:

void ChangeImage(Bitmap b) {

  Image oldImage = PictureBox.Image;

  PictureBox.Image = b;

  if (oldImage != null) {
    oldImage.Dispose();
  }

}

未正确配置的位图必须先完成,然后才能收集。有一个后台线程可以最终确定需要收集的对象,但是如果你放弃对象的速度比那个线程可以处理它们的速度快,那么你的内存就会用完。

当位图正确配置时,它会成为一个常规的托管对象,只要垃圾收集器想要它就可以立即收集。