Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the alternative to the GarbageCollector?

I have the following code, which will generate an OutOfMemoryException when run:

public partial class MainWindow : Window
{
    private DrawingVisual myVisual = new DrawingVisual();

    public MainWindow()
    {
        InitializeComponent();
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        myVisual = GetVisual();
        graphicsCanvas.AddVisual(myVisual);
    }

    private void Graphics_Canvas_MouseMove(object sender, MouseEventArgs e)
    {
        //get the data visual:
        DrawingVisual tempVisual = GetVisual();

        //first clear the current display data:
        graphicsCanvas.RemoveVisual(myVisual);

        //get the data visual:
        myVisual = tempVisual;

        graphicsCanvas.AddVisual(myVisual);

        //GC.Collect();
    }

    private DrawingVisual GetVisual()
    {
        double width = graphicsCanvas.ActualWidth;
        double height = graphicsCanvas.ActualHeight;

        DrawingVisual dV = new DrawingVisual();

        Rect clipRect = new Rect(0, 0, width, height);

        dV.Clip = new RectangleGeometry(clipRect);

        using (DrawingContext dC = dV.RenderOpen())
        {
            RenderTargetBitmap rTB = new RenderTargetBitmap((int)width, (int)height, 96, 96, PixelFormats.Pbgra32);

            if (rTB.CanFreeze)
            {
                rTB.Freeze();
            }

            dC.DrawImage(rTB, clipRect);
        }

        return dV;
    }
}

Where a Graphics_Canvas is defined as follows:

class Graphics_Canvas : Canvas
{
    private List<DrawingVisual> visuals = new List<DrawingVisual>();

    protected override int VisualChildrenCount
    {
        get { return visuals.Count; }
    }

    protected override Visual GetVisualChild(int index)
    {
        return visuals[index];
    }

    public void AddVisual(DrawingVisual visual)
    {
        visuals.Add(visual);

        base.AddVisualChild(visual);
        base.AddLogicalChild(visual);
    }

    public bool ContainsVisual(DrawingVisual visual)
    {
        return visuals.Contains(visual);
    }

    public bool HasVisuals
    {
        get { return visuals.Count > 0; }
    }

    public void RemoveAllVisuals()
    {
        for (int i = 0; i < visuals.Count; i++)
        {
            RemoveFromLogicalTree(visuals[i]);
        }

        visuals.Clear();
    }

    private void RemoveFromLogicalTree(Visual visual)
    {
        RemoveLogicalChild(visual);
        RemoveVisualChild(visual);
    }

    public void RemoveLastVisual()
    {
        if (visuals.Count > 0)
        {
            int index = visuals.Count - 1;

            RemoveFromLogicalTree(visuals[index]);
            visuals.Remove(visuals[index]);
        }     
    }

    public void RemoveVisual(DrawingVisual visual)
    {
        RemoveFromLogicalTree(visual);
        visuals.Remove(visual);            
    }
}

And the XAML to create the Window is like this:

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:csMemoryLeakTestProject" x:Class="csMemoryLeakTestProject.MainWindow"
    Title="MainWindow" 
    Background="Gray"
    Height="600" Width="800"
    WindowStartupLocation="CenterScreen"
    Loaded="Window_Loaded">
    <Grid>
        <local:Graphics_Canvas Margin="12" Background="White" x:Name="graphicsCanvas" MouseMove="Graphics_Canvas_MouseMove"/>
    </Grid>
</Window>

Now, this is just an example to illustrate my point, which is that I don't understand what the alternative is here to using the Garbage Collector...

If you run the program, and keep moving the mouse over the Graphics_Canvas, the memory use builds and builds and builds until you get an OutOfMemoryException. If you add in GC.Collect() where I've commented it out, this doesn't happen (although the memory does increase by a small amount, for reasons beyond me, and hopefully linked with my problem), and the program continues to function.

So, why does the GC not kick in and clear this memory and stop the exception occurring? If I'm doing some very fundamental error I'd be very happy for someone to point it out to me so that I can move beyond this.

I have seen many times on this website and others advice from programmers saying "never use the Garbage Collector". I want to conform to best practice, but I don't see in situations like this what else I could do.

like image 440
Greg Avatar asked Mar 25 '13 09:03

Greg


1 Answers

It's not a fault of GC that the program is not able to manage much memory. In your program you do:

private void Graphics_Canvas_MouseMove(object sender, MouseEventArgs e)
{
   ....
    //ADD ELEMENTS ON EVERY MOVE !
    graphicsCanvas.AddVisual(myVisual);

    //GC.Collect();
}

Tou add an element to every mouse move, so increase the collection size. What you would expect happen ?

So, in 90% cases, the solution for these kind of problems, is rearchitect your code.

Example:

It's hardly possible that you need on MouseMove add every time a new element to a chidren collection of visual. May be it's a case to reuse already present one.

Esplicit use of GC is not suggested, but sometimes we need to use it. But, I repeat, I can hardly believe that in this case you need to manage program in that way.

like image 164
Tigran Avatar answered Nov 10 '22 00:11

Tigran