我正在开发一个连接到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.
}
}
}
答案 0 :(得分:1)
您的代码中某处Image
(例如Bitmap
)很可能没有被处理掉。 Bitmap
扩展了Image
,它实现了IDisposable
,这意味着当你完成它时需要在其上调用Dispose()
(通常用{{1}包裹它声明)。您不会在某处处置using
或Bitmap
,以便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();
}
}
未正确配置的位图必须先完成,然后才能收集。有一个后台线程可以最终确定需要收集的对象,但是如果你放弃对象的速度比那个线程可以处理它们的速度快,那么你的内存就会用完。
当位图正确配置时,它会成为一个常规的托管对象,只要垃圾收集器想要它就可以立即收集。