Почему растровые изображения остаются в памяти, пока я не вызову GC.Collect?

Я разрабатываю приложение, которое подключается к камере 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.
        }
    }
}

person christophos    schedule 01.07.2014    source источник
comment
Вам нужно позвонить PictureBox.Image.Dispose(), а не PictureBox.Dispose().   -  person Michael Liu    schedule 01.07.2014
comment
@MichaelLiu Ты прав. Спасибо.   -  person christophos    schedule 01.07.2014


Ответы (3)


Очень вероятно, что где-то в вашем коде есть Image, например Bitmap< /a> не удаляется. Bitmap расширяет Image, который реализует IDisposable, а это означает, что вам нужно вызвать Dispose(), когда вы закончите с ним (часто оборачивая его оператором using). Вы не размещаете Bitmap или Image где-то, поэтому сборщик мусора завершает его, когда может (или в этом случае, когда вы явно вызываете сборщик мусора).

Как только сборщик мусора определяет, что на класс больше нет ссылок, он становится доступным для очистки... Перед очисткой он проверяет наличие финализатора. Если финализатор существует, класс помещается в специальную очередь финализатора GC, которая запустит финализатор перед очисткой ресурсов/памяти. Большинство IDisposable классов имеют финализаторы, которые позволяют сборщику мусора выполнять работу вызова Dispose() на тот случай, если вы забыли удалить класс вручную. Кажется, это то, что происходит с вашим кодом, но, не видя ВСЕХ классов, я могу только догадываться, что не расположено (и понятия не имею, где).

EDIT: у меня есть предположение. Бьюсь об заклад, вызов PictureBox.Dispose() не избавит от PictureBox.Image

person Haney    schedule 01.07.2014
comment
Отсутствует Dispose() - как указал @MichealLiu, эта строка PictureBox.Dispose(); должно быть PictureBox.Image.Dispose(). Я просто не мог видеть это сам по какой-то причине. - person christophos; 01.07.2014
comment
У меня была такая же проблема, но это было связано с объектом Graphics. Эй, ребята, всегда используйте оператор using! :) - person Pedro77; 17.05.2016

Если объект реализует IDisposable, то вы обязательно должны вызвать для него Dispose, но удаление объекта не освобождает занимаемую им память. Он освобождает такие вещи, как, в данном случае, дескриптор изображения. Такие ресурсы должны быть сначала освобождены, прежде чем память может быть восстановлена, поэтому удаление по-прежнему помогает.

Когда GC запускается, если объект не был удален, он должен сначала завершить его, а это означает, что он должен ждать дольше, чтобы освободить память. Если объект был удален, память освобождается, как только запускается GC.

Однако GC работает в фоновом режиме. Если ваше приложение занято выделением все большего и большего объема памяти, сборщик мусора никогда не сможет запуститься и освободить ее, независимо от того, удаляете ли вы объекты или нет. В таких случаях необходимо время от времени явно вызывать сборщик мусора. Создание нескольких образов — наиболее распространенный сценарий, требующий явного вызова сборщика мусора.

Стоит отметить, что ВСЕ объекты остаются в памяти до тех пор, пока GC не запустится и не очистит их, вне зависимости от того, реализует объект IDisposable или нет. Однако обычно вы этого не замечаете, потому что у большинства приложений достаточно времени простоя, чтобы позволить сборщику мусора запускаться неявно и освобождать эту память. В этом отношении в ваших Bitmap объектах нет ничего особенного.

person jmcilhinney    schedule 01.07.2014

Вы размещаете поле изображения вместо изображения. Даже если это приведет к размещению изображения в поле изображения, это будет сделано только в первый раз. После этого окно изображения находится в удаленном состоянии, и повторный вызов Dispose ничего не сделает.

Вы должны получить ссылку на изображение из окна изображения и удалить ее, когда она больше не используется:

void ChangeImage(Bitmap b) {

  Image oldImage = PictureBox.Image;

  PictureBox.Image = b;

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

}

Растровое изображение, которое размещено неправильно, должно быть завершено, прежде чем его можно будет собрать. Существует фоновый поток, который дорабатывает объекты, которые необходимо собрать, но если вы отказываетесь от объектов быстрее, чем этот поток может позаботиться о них, у вас закончится память.

Когда растровое изображение удалено правильно, оно становится обычным управляемым объектом, который может быть собран сразу же, когда этого захочет сборщик мусора.

person Guffa    schedule 01.07.2014