Simple WPF sample causes uncontrolled memory growth

后端 未结 4 762
天命终不由人
天命终不由人 2021-02-02 16:49

I have boiled down an issue I\'m seeing in one of my applications to an incredibly simple reproduction sample. I need to know if there\'s something amiss or something I\'m missi

相关标签:
4条回答
  • 2021-02-02 17:26

    WPF in .NET 3 and 3.5 has an internal memory leak. It only triggers under certain situations. We could never figure out exactly what triggers it, but we had it in our app. Apparently it's fixed in .NET 4.

    I think it's the same as the one mentioned in this blog post

    At any rate, putting the following code in the App.xaml.cs constructor solved it for us

    public partial class App : Application
    {
       public App() 
       { 
           new HwndSource(new HwndSourceParameters()); 
       } 
    }
    

    If nothing else solves it, try that and see

    0 讨论(0)
  • Normally in .NET GC gets triggered on object allocation upon crossing a certain threshold, it does not depend on message pumps (I can't imagine it's different with WPF).

    I suspect that Canvas objects are somehow rooted deep inside or something. If you do c.Children.Clear() right before the BuildCanvas method finishes, the memory growth slows down dramatically.

    Anyway, as a commenter noted here, such usage of framework elements is pretty unusual. Why do you need so many Canvases?

    0 讨论(0)
  • 2021-02-02 17:35

    Edit 2: Obviously not the answer, but was part of the back-and-forth among answers and comments here, so I'm not deleting it.

    The GC never gets a chance to collect those objects because your loop and its blocking calls never end, and therefore the message pump and events never get their turn. If you used a Timer of some sort so that messages and events actually have a chance to process, you probably wouldn't be able to eat up all your memory.

    Edit: The following does not eat up my memory as long as the interval is greater than zero. Even if the interval is just 1 Tick, as long as it isn't 0. If it's 0, we're back to the infinite loop.

    public partial class Window1 : Window {
        Class1 c;
        DispatcherTimer t;
        int count = 0;
        public Window1() {
            InitializeComponent();
    
            t = new DispatcherTimer();
            t.Interval = TimeSpan.FromMilliseconds( 1 );
            t.Tick += new EventHandler( t_Tick );
            t.Start();
        }
    
        void t_Tick( object sender, EventArgs e ) {
            count++;
            BuildCanvas();
        }
    
        private static void BuildCanvas() {
            Canvas c = new Canvas();
    
            Line line = new Line();
            line.X1 = 1;
            line.Y1 = 1;
            line.X2 = 100;
            line.Y2 = 100;
            line.Width = 100;
            c.Children.Add( line );
    
            c.Measure( new Size( 300, 300 ) );
            c.Arrange( new Rect( 0, 0, 300, 300 ) );
        }
    }
    
    0 讨论(0)
  • 2021-02-02 17:36

    I was able to reproduce your problem using the code you provided. Memory keeps growing because the Canvas objects are never released; a memory profiler indicates that the Dispatcher's ContextLayoutManager is holding on to them all (so that it can invoke OnRenderSizeChanged when necessary).

    It seems that a simple workaround is to add

    c.UpdateLayout()
    

    to the end of BuildCanvas.

    That said, note that Canvas is a UIElement; it's supposed to be used in UI. It's not designed to be used as an arbitrary drawing surface. As other commenters have already noted, the creation of thousands of Canvas objects may indicate a design flaw. I realise that your production code may be more complicated, but if it's just drawing simple shapes on a canvas, GDI+-based code (i.e., the System.Drawing classes) may be more appropriate.

    0 讨论(0)
提交回复
热议问题