1

I've got a WPF program that uses a Canvas within a Window, over-ridden to become a Graphics_Canvas, which looks like this:

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++)
        {
            base.RemoveVisualChild(visuals[i]);
            base.RemoveLogicalChild(visuals[i]);
        }

        visuals.Clear();
    }

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

            base.RemoveVisualChild(visuals[index]);
            base.RemoveLogicalChild(visuals[index]);
            visuals.Remove(visuals[index]);
        }     
    }

    public void RemoveVisual(DrawingVisual visual)
    {
        base.RemoveVisualChild(visual);
        base.RemoveLogicalChild(visual);
        visuals.Remove(visual);            
    }
}

(I did get that from somewhere online but I cannot remember where as it was a while ago.)

Anyway, the program allows the user to pan the graphics displayed on the Graphics_Canvas by dragging with the middle mouse, and this will continually (as long as they keep panning) trigger something like this:

            //get the data visual:
            DrawingVisual tempVisual = GetDataDrawingVisual();

            //first clear the current display data:
            myCanvas.RemoveVisual(dataVisual);

            //get the data visual:
            dataVisual = tempVisual;

            myCanvas.AddVisual(dataVisual);

So I continually remove and then re-add the dataVisual.

What I notice when I look at memory use is that panning around causes memory use to increase, and while it does drop down again, it doesn't go all the way to what it was before. This is even more pronounced when nothing is on the display at all, and the memory use of continually re-adding effectively nothing can rise to hundreds of MB, again dropping mostly when panning stops.

For information, I should add that GetDataDrawingVisual() returns a Visual onto which is drawn a RenderTargetBitmap, which itself is not retained outside of that method.

My question is why does this cause memory usage to vary so much?

Currently this is more of a curious nuisance, but I can foresee that it could be a problem if the available memory was more limited to begin with.

Any insights would be greatly appreciated.

* UPDATE (21/03/2013) *

From experimenting with the Graphics_Canvas, it appears that the following method makes a fair impact on how much memory is not released:

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

If I just return a new DrawingVisual() instead as a test, memory goes back to pretty much where it was after re-drawing ends.

However, removing this method just means that the Canvas won't run, and I'll get an error. Could it be that somewhere behind the scenes .NET is making a reference to the Visual returned from this method and then not de-referencing it later? How could I sort this? (Note: I never explicitly call this method in my code, it is called from somewhere else).

Greg
  • 1,673
  • 4
  • 20
  • 27
  • In the code that you show, the visual that you remove (dataVisual), might not be the same as the one you are adding. Could you show getDataDrawingVisual code ? – GameAlchemist Mar 20 '13 at 12:23
  • @VincentPiel True, it will not always be the same as I will be changing the graphics on the Canvas. But for the example above I was generating a completely blank image all the time, and it is because nothing was changing in the content of the Visual that makes me think there is a problem somewhere. The code for the method expands into loads of sub-routines so is quite verbose, and what is done depends on user settings and the number of files loaded into the program, so adding it would probably not be easy or particularly helpful. – Greg Mar 20 '13 at 12:29
  • If you're creating a new Canvas object at all, why would you expect memory use not to increase? – Peter Ritchie Mar 20 '13 at 12:38
  • @PeterRitchie I'm not creating a new Canvas. The Canvas is created only once on application startup, after that I just add and remove Visuals. – Greg Mar 20 '13 at 12:39
  • 2
    ok so just another thought i had : memory leaks might be caused by events handlers not removed. But out of this general advice, it seems you should use a memory profiling tool to know what's not being released. CLR Memory Profiler for instance. – GameAlchemist Mar 20 '13 at 12:42
  • @VincentPiel Thanks. I don't have any events associated with the Visual or Canvas. That said, that is something I have been looking into elsewhere in the program, and I try very hard to ensure that events are always unsubscribed when I no longer need them. – Greg Mar 20 '13 at 14:35

1 Answers1

1

I think the reason why your memory usage varies so much is that the garbage collector doesn't kick in right away. So while you are dragging the objects around is building up memory usage until it runs.

You could force the garbage collection by doing:

GC.Collect();
GC.WaitForPendingFinalizers();

That way you will know for sure.

About why your memory doesn't go all the way back: Maybe you are keeping references to some of your visuals and those don't get collected? Maybe something in the other parts of the program is building up memory?

You may have a memory leak, you can check this article about WPF Performance and .NET Framework Client Profile

Dzyann
  • 5,062
  • 11
  • 63
  • 95
  • I have a couple of cases in my program where I manually call the GC, but these are just when I have closed large files. I'm weary of doing this as the predominant view on here and elsewhere seems to be never to manually call the GC. – Greg Mar 20 '13 at 12:45
  • 1
    Calling the GC explicitly it is considered a bad practice. Make sure that you have no other way to release the resources in that case. You can check this article: http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx The section "Explicit release of Resources" Suggest to implement IDisposable instead. Here you have an article on how to do that: http://www.codeproject.com/Articles/413887/Understanding-and-Implementing-IDisposable-Interfa – Dzyann Mar 20 '13 at 16:04
  • Thanks, I'm not using the GC in this case. Also, DrawingVisual and RenderTargetBitmap do not implement IDisposable, so I can't do that here. From some further testing, this seems to be being caused by the RenderTargetBitmap. I did a test returning an empty DrawingVisual with no memory increase, but when I draw an empty RenderTargetBitmap onto that, memory increased rapidly with constant redrawing. When I stop, it drops down but not to the original level. – Greg Mar 20 '13 at 16:52
  • 1
    When I mentioned IDisposable I was refering to the other cases where you mentioned you were calling directly the GC, just a thought. Regarding RenderTargetBitmap, the memory usage is related to when the GC is kicking in, and you may have some memory Leak. Regarding the high memory comsuption using RenderTargetBitmap, check this out: http://stackoverflow.com/questions/6713868/rendertargetbitmap-memory-leak and http://stackoverflow.com/questions/6449297/program-takes-too-much-memory/6449558#6449558 – Dzyann Mar 20 '13 at 18:58
  • Thanks again. It's interesting about the memory use of DrawingContext/DrawingVisual, however in my case I think Shapes are not the solution, as I am often drawing quite complex geometries with hundreds of points, and there could then be hundreds of these... – Greg Mar 21 '13 at 08:08