Mengapa bitmap tetap ada di memori kecuali saya memanggil GC.Collect?

Saya sedang mengembangkan aplikasi yang terhubung ke kamera GigEVision, dan mengambil gambar darinya. Saat ini saya menggunakan Pleora eBus SDK dengan C#.NET.

Kode di bawah ini hanyalah aplikasi pengujian untuk koneksi kamera - kode ini dapat melakukan streaming gambar, tetapi cepat kehabisan memori kecuali saya memanggil GC.Collect(); Perlu diperhatikan bahwa gambar yang dialirkan berukuran besar (4096x3072), sehingga kerusakan terjadi cukup cepat.

Awalnya saya curiga bahwa tidak menelepon Dispose() adalah masalahnya. Namun, saya dapat memanggil Dispose() pada setiap gambar tepat sebelum menghilangkan referensi ke gambar tersebut, dan itu tidak menyelesaikan masalah.

Saya juga telah mencoba secara eksplisit melepaskan buffer yang masuk ke panggilan balik thread tampilan, tetapi itu tidak berpengaruh.

Bisakah saya mendapatkan kembali ingatan saya dengan cara yang lebih elegan?

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 sumber
comment
Anda perlu menelepon PictureBox.Image.Dispose(), bukan PictureBox.Dispose().   -  person Michael Liu    schedule 01.07.2014
comment
@MichaelLiu Anda benar. Terima kasih.   -  person christophos    schedule 01.07.2014


Jawaban (3)


Sangat mungkin bahwa di suatu tempat dalam kode Anda terdapat Image seperti Bitmap< /a> tidak dibuang. Bitmap memperluas Image yang mengimplementasikan IDisposable yang berarti Anda perlu memanggil Dispose() setelah selesai (seringkali dengan membungkusnya dengan pernyataan using). Anda tidak membuang Bitmap atau Image di suatu tempat sehingga GC menyelesaikannya jika bisa (atau dalam kasus ini ketika Anda secara eksplisit memanggil GC).

Setelah GC menentukan bahwa suatu kelas tidak lagi direferensikan, kelas tersebut tersedia untuk dibersihkan... Sebelum membersihkannya, ia memeriksa finalizer. Jika ada finalizer, kelas ditempatkan dalam antrian finalizer GC khusus yang akan menjalankan finalizer sebelum membersihkan sumber daya/memori. Sebagian besar kelas IDisposable memiliki finalizer yang memungkinkan GC melakukan pekerjaan panggilan Dispose() jika Anda lupa membuang sendiri kelas tersebut secara manual. Tampaknya inilah yang terjadi pada kode Anda, tetapi tanpa melihat SEMUA kelas saya hanya bisa menebak apa yang tidak dibuang (dan tidak tahu di mana).

EDIT: Tapi saya punya tebakannya. Saya yakin panggilan PictureBox.Dispose() tidak membuang PictureBox.Image

person Haney    schedule 01.07.2014
comment
Ini adalah Dispose() yang hilang - seperti yang ditunjukkan @MichealLiu, baris ini PictureBox.Dispose(); seharusnya PictureBox.Image.Dispose(). Saya tidak bisa melihatnya sendiri karena beberapa alasan. - person christophos; 01.07.2014
comment
Saya mengalami masalah yang sama, tapi itu disebabkan oleh objek Grafik. Hai teman-teman, selalu gunakan pernyataan penggunaan! :) - person Pedro77; 17.05.2016

Jika suatu objek mengimplementasikan IDisposable maka Anda harus benar-benar memanggil Dispose tetapi membuang suatu objek tidak akan melepaskan memori yang ditempatinya. Ini melepaskan hal-hal seperti, dalam hal ini, pegangan gambar. Sumber daya tersebut harus dilepaskan terlebih dahulu, sebelum memori dapat diperoleh kembali, jadi membuangnya tetap membantu.

Saat GC berjalan, jika suatu objek belum dibuang maka harus diselesaikan terlebih dahulu, artinya harus menunggu lebih lama untuk mendapatkan kembali memorinya. Jika objek telah dibuang, memori akan diambil kembali segera setelah GC berjalan.

GC berjalan di latar belakang. Jika aplikasi Anda sibuk mengalokasikan lebih banyak memori maka GC tidak akan pernah mendapat kesempatan untuk menjalankan dan mengambilnya kembali, terlepas dari apakah Anda membuang objek atau tidak. Dalam kasus seperti ini, Anda perlu memanggil GC secara eksplisit dari waktu ke waktu. Membuat banyak gambar adalah skenario paling umum yang memerlukan pemanggilan GC eksplisit.

Perlu diperhatikan bahwa SEMUA objek tetap berada di memori hingga GC berjalan dan membersihkannya, baik objek tersebut mengimplementasikan IDisposable atau tidak. Biasanya Anda tidak menyadarinya, karena sebagian besar aplikasi memiliki waktu henti yang cukup untuk memungkinkan GC berjalan secara implisit dan mendapatkan kembali memori tersebut. Tidak ada yang istimewa tentang objek Bitmap Anda dalam hal ini.

person jmcilhinney    schedule 01.07.2014

Anda membuang kotak gambar, bukan gambarnya. Meskipun tindakan tersebut akan membuang gambar tersebut ke dalam kotak gambar, tindakan tersebut hanya akan dilakukan untuk pertama kalinya. Setelah itu kotak gambar berada dalam keadaan dibuang dan memanggil Dispose lagi tidak akan menghasilkan apa-apa.

Anda harus mengambil referensi gambar dari kotak gambar dan membuangnya setelah tidak digunakan lagi:

void ChangeImage(Bitmap b) {

  Image oldImage = PictureBox.Image;

  PictureBox.Image = b;

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

}

Bitmap yang tidak dibuang dengan benar harus diselesaikan sebelum dapat dikumpulkan. Ada thread latar belakang yang menyelesaikan objek yang perlu dikumpulkan, tetapi jika Anda meninggalkan objek lebih cepat daripada kemampuan thread tersebut untuk menanganinya, Anda akan kehabisan memori.

Jika bitmap dibuang dengan benar, bitmap tersebut akan menjadi objek terkelola reguler yang dapat langsung dikumpulkan kapan pun pemulung menginginkannya.

person Guffa    schedule 01.07.2014