1

I am using AForge to capture video from a camera and display it in a WinForms PictureBox. AForge supplies an event that gets triggered on every new frame--so, for my camera, 30 times per second.

I'm finding that even when I am careful to dispose of the PictureBox's image, memory usage climbs rapidly (and in a weird pattern... see image below). My guess is that the event is being called more often than it can dispose of the bitmap, but I'm not sure.

Here is what I have inside the newframe event. I'm using Monitor.TryEnter per this answer, to avoid having events pile up waiting for the PictureBox to unlock. I had hoped that setting the timeout to 1 millisecond would effectively prevent any events from piling up.

private void Camera_NewFrame(object sender, NewFrameEventArgs e)
    {
        if (Monitor.TryEnter(_pictureBox, millisecondsTimeout: 1))
        {
            try
            {
                _pictureBox.Image?.Dispose();
                _pictureBox.Image = AForge.Imaging.Image.Clone(newImage);
            }
            finally
            {
                Monitor.Exit(_pictureBox);
            }
        }
    }

This is the memory usage pattern that results when I run the code.

enter image description here

  1. Video capture started
  2. Unknown event causes memory usage to increase
  3. Unknown event causes memory usage to stabilize
  4. Unknown event causes memory usage to increase

And that repeats. Note that if I change the TryEnter timeout to a higher value, the stable memory usage periods get shorter.

If anyone can offer a solution for this memory leak, I would be very grateful. I did see a similar question here, but it wasn't really answered: C# Picturebox memory leak

gpph
  • 33
  • 5
  • 1
    Increased memory usage does not necessarily indicate a memory leak. Managed environments perform garbage collection at intervals, so rapidly allocating large amounts of memory between those intervals can result in a large increase in memory usage, and `Dispose` doesn't actually deallocate memory. Can you prove that the memory is never collected, *cannot be collected*, and is in fact leaked? – madreflection Jul 13 '22 at 19:01
  • Isn't the `Camera_NewFrame` event raised in a ThreadPool Thread? How can you reference and set properties of a Control there? Why are you using a PictureBox Control as the object on which the lock is acquired? -- You should use a Bitmap object, set as the source of the `Image` Property of your PictureBox. When a new Frame arrives, you draw the Image onto this bitmap and `BeginInvoke()` the `Invalidate` method of a Control. No need to dispose of the Bitmap object, except in the end (when the Parent Form closes). -- You most probably don't need any lock there. – Jimi Jul 14 '22 at 00:05
  • @madreflection I can prove that over the course of about four minutes, memory continually increases to 2 GB without ever decreasing. Does that answer your question? – gpph Jul 14 '22 at 17:29
  • No, that's effectively just repeating the original statement. That doesn't prove that the memory is leaked. It could just as easily be that it's accessible by something you don't know about -- that may not even be your clone of the image that's being held -- and so the GC won't release the memory. That's expected behavior, not a leak. In other words, it's very difficult to prove an actual leak in a managed environment, and impossible to do so by looking at memory usage alone. – madreflection Jul 14 '22 at 17:36
  • @jimi Do you have an example of code that does this? – gpph Jul 14 '22 at 18:10
  • @madreflection I guess my question, then, is how I can collect it. Do you have some concrete next steps that I can try? – gpph Jul 14 '22 at 18:11
  • I don't, but Jimi's suggestion should mitigate the problem. If you're not causing memory usage to increase in the first place, you never have to track it down. – madreflection Jul 14 '22 at 18:18

1 Answers1

1

This is not the perfect way to do this, but I found a workaround that solves the problem of skyrocketing memory usage. I basically just limited the frequency with which the PictureBox image can update.

    private const int MAX_UPDATE_TIME_MS = 70; // Effectively limits displayed video frame rate. Without this, events pile up faster than we can dispose of the PictureBox's current image, leading to a HUGE memory leak
    private DateTime _lastUpdateTimeStamp = DateTime.Now;
    private void Camera_NewFrame(object sender, NewFrameEventArgs e)
    {
        if (DateTime.Now < _lastUpdateTimeStamp.AddMilliseconds(MAX_UPDATE_TIME_MS))
        {
            return;
        }
        else
        {
            _lastUpdateTimeStamp = DateTime.Now;

            try
            {
                UpdatePictureBoxImage(_pictureBox, e.Frame);
            }
            catch
            {
                // Do nothing, else PictureBox could freeze
            }
        }
    }

    private void UpdatePictureBoxImage(
        PictureBox pictureBox,
        Bitmap newImage)
    {
        if (Monitor.TryEnter(pictureBox, millisecondsTimeout: 1))
        {
            try
            {
                pictureBox.Image?.Dispose();
                pictureBox.Image = AForge.Imaging.Image.Clone(newImage);
            }
            finally
            {
                Monitor.Exit(pictureBox);
            }
        }
    }

Additional note specific to AForge

When stopping the video stream, AForge says you should do something like the StopVideoCapture() method I wrote below. I found that the memory AForge was using to control the camera, which was not insignificant, did not get deallocated in a timely way. So I added GC.Collect() to a new method called DisposeVideoSource() that I run when I'm done with the camera.

    public void StopVideoCapture()
    {
        while (_videoCaptureDevice.IsRunning)
        {
            _videoCaptureDevice.SignalToStop();
            _videoCaptureDevice.WaitForStop();
        }
    }

    public void DisposeVideoSource()
    {
        StopVideoCapture();
        GC.Collect();
    }

Before adding GC.Collect():

enter image description here

After adding GC.Collect():

enter image description here

gpph
  • 33
  • 5
  • Thanks so much for providing the solution above, forcing the garbage colection to clean up. I was chasing my tail trying to find some memory leak as I had wated 10-15 minutes leaving my app running to see if GC would automatically collect my disposed VideoCaptureDevice, and it still had not after that amount of time so I was convinced something was still referencing the capture device or I had not cleaned up correctly. Manually calling GC.Collect() after my method executed immediately dropped the memory usage which is exactly what I needed. – DBS Oct 23 '22 at 01:17
  • @DBS Thanks for letting me know it was helpful! Glad your problem is solved. – gpph Oct 24 '22 at 19:28