【问题标题】:Why do bitmaps stay in memory unless I call GC.Collect?除非我调用 GC.Collect,否则为什么位图会留在内存中?
【发布时间】:2014-07-01 00:01:48
【问题描述】:

我正在开发一个连接到 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.
        }
    }
}

【问题讨论】:

  • 你需要打电话给PictureBox.Image.Dispose(),而不是PictureBox.Dispose()
  • @MichaelLiu 你是对的。谢谢。

标签: c# .net bitmap


【解决方案1】:

很可能在您的代码中的某处没有处理Image,例如BitmapBitmap 扩展了 Image 实现了 IDisposable,这意味着当你完成它时需要调用 Dispose()(通常是用 using 语句包装它)。您没有在某处处置 BitmapImage,因此 GC 会在可能时(或者在这种情况下,当您显式调用 GC 时)完成它。

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

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

【讨论】:

  • 这是一个缺失的 Dispose() - 正如@MichealLiu 指出的那样,这一行 PictureBox.Dispose();应该是 PictureBox.Image.Dispose()。由于某种原因,我自己看不到它。
  • 我遇到了同样的问题,但这是由于 Graphics 对象。嘿伙计们,总是使用“使用”声明! :)
【解决方案2】:

如果一个对象实现了IDisposable,那么你绝对应该在它上面调用Dispose,但是释放一个对象并不会释放它占用的内存。在这种情况下,它会释放图像句柄之类的东西。此类资源必须先释放,然后才能回收内存,因此处置仍然有帮助。

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

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

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

【讨论】:

    【解决方案3】:

    您正在处理图片框而不是图像。即使这会将图像放置在图片框中,它也只会在第一次这样做。之后图片框处于释放状态,再次调用Dispose 将无济于事。

    您应该从图片框中获取图像参考,并在不再使用时将其丢弃:

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

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

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

    【讨论】:

      猜你喜欢
      • 2014-02-20
      • 2013-07-19
      • 1970-01-01
      • 2023-03-22
      • 2020-10-19
      • 1970-01-01
      • 2021-05-15
      • 2019-10-15
      • 1970-01-01
      相关资源
      最近更新 更多