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

#c# #.net #bitmap

#c# #.net #растровое изображение

Вопрос:

Я разрабатываю приложение, которое подключается к камере GigEVision и извлекает из нее изображения. В настоящее время я использую Pleora eBus SDK с C#.NET .

Приведенный ниже код является всего лишь тестовым приложением для подключения камеры — он может передавать изображения, но быстро исчерпывает память, если я не вызываю GC.Collect(); Стоит отметить, что передаваемые изображения большие (4096×3072), поэтому сбой происходит довольно быстро.

Сначала я подозревал, что проблема не в вызове 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.
        }
    }
}
  

Комментарии:

1. Вам нужно вызвать PictureBox.Image.Dispose() , а не PictureBox.Dispose() .

2. @MichaelLiu Ты прав. Спасибо.

Ответ №1:

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

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

РЕДАКТИРОВАТЬ: хотя у меня есть предположение. Бьюсь об заклад, PictureBox.Dispose() вызов не удаляет PictureBox.Image

Комментарии:

1. Отсутствует функция Dispose() — как указал @MichealLiu, эта строка PictureBox.Dispose(); должна быть PictureBox. Изображение. Dispose(). По какой-то причине я просто не мог увидеть это самостоятельно.

2. У меня была та же проблема, но это было связано с графическим объектом. Эй, ребята, всегда используйте оператор «using»! 🙂

Ответ №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();
  }

}
  

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

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