简单的WPF示例导致不受控制的内存增长 [英] Simple WPF sample causes uncontrolled memory growth

查看:2034
本文介绍了简单的WPF示例导致不受控制的内存增长的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经我看到在我的应用程序之一的问题归结为一个令人难以置信的简单再生产样本。我需要知道,如果有什么东西不对劲或者我丢失的东西。

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 missing.

不管怎样,下面是代码。该行为是代码运行和稳步增长在内存中,直到它与一个OutOfMemoryException崩溃。这需要一段时间,但该行为是对象被分配并没有被垃圾收集。

Anyway, below is the code. The behavior is that the code runs and steadily grows in memory until it crashes with an OutOfMemoryException. That takes a while, but the behavior is that objects are being allocated and are not being garbage collected.

我已经采取的内存转储和跑一些事情!gcroot以及用于蚂蚁搞清楚的问题是什么,但我已经在这了同时,需要一些新的眼光。

I've taken memory dumps and ran !gcroot on some things as well as used ANTS to figure out what the problem is, but I've been at it for a while and need some new eyes.

这再现样本是创建一个画布,并增加了一个行给它一个简单的控制台应用程序。为此,它会不断。这是所有的代码一样。它现在又睡每天以确保CPU并非如此征税您的系统没有反应(并确保有一个与不是的GC能够运行任何怪事)。

This reproduction sample is a simple console application that creates a Canvas and adds a Line to it. It does this continually. This is all the code does. It sleeps every now and again to ensure that the CPU is not so taxed that your system is unresponsive (and to ensure there's no weirdness with the GC not being able to run).

任何人有什么想法?我已经试过这与.NET 3.0只,.NET 3.5和.NET也是3.5 SP1和相同的行为发生在所有三种环境。

Anyone have any thoughts? I've tried this with .NET 3.0 only, .NET 3.5 and also .NET 3.5 SP1 and the same behavior occurred in all three environments.

另外请注意,我。已经把这个代码放在一个WPF应用程序项目,以及,并引发点击一个按钮的代码,也发生在那里。

Also note that I've put this code in a WPF application project as well and triggered the code in a button click and it occurs there too.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows.Shapes;
using System.Windows;

namespace SimplestReproSample
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            long count = 0;
            while (true)
            {
                if (count++ % 100 == 0)
                {
                    // sleep for a while to ensure we aren't using up the whole CPU
                    System.Threading.Thread.Sleep(50);
                }
                BuildCanvas();
            }
        }

        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
        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));
        }
    }
}

请注意:下面的第一个答案是有点过,基地自我明确表示已经是一个WPF应用程序的按钮单击事件过程中发生此相同的行为。我没有明确说明,但是,在我的应用程序只能做迭代(比如1000)的数量有限。做这种方式将允许GC运行你点击周围的应用程序。另请注意,我明确表示我已经采取了内存转储,发现我的对象是通过!gcroot扎根。我也同意了GC将无法运行。 GC没有对我的控制台应用程序的主线程中运行,特别是因为我是双核的机器,这意味着并发工作站GC是活跃。消息泵,但是,是的。

NOTE: the first answer below is a bit off-base since I explicitly stated already that this same behavior occurs during a WPF application's button click event. I did not explicitly state, however, that in that app I only do a limited number of iterations (say 1000). Doing it that way would allow the GC to run as you click around the application. Also note that I explicitly said I've taken a memory dump and found my objects were rooted via !gcroot. I also disagree that the GC would not be able to run. The GC does not run on my console application's main thread, especially since I'm on a dual core machine which means the Concurrent Workstation GC is active. Message pump, however, yes.

要证明这一点,下面是一个运行于DispatcherTimer测试WPF应用程序版本。它100ms的计时器间隔期间执行1000次迭代。 。足够多的时间来处理任何消息了泵和保持CPU使用率较低

To prove the point, here's a WPF application version that runs the test on a DispatcherTimer. It performs 1000 iterations during a 100ms timer interval. More than enough time to process any messages out of the pump and keep the CPU usage low.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;

namespace SimpleReproSampleWpfApp
{
    public partial class Window1 : Window
    {
        private System.Windows.Threading.DispatcherTimer _timer;

        public Window1()
        {
            InitializeComponent();

            _timer = new System.Windows.Threading.DispatcherTimer();
            _timer.Interval = TimeSpan.FromMilliseconds(100);
            _timer.Tick += new EventHandler(_timer_Tick);
            _timer.Start();
        }

        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
        void RunTest()
        {
            for (int i = 0; i < 1000; i++)
            {
                BuildCanvas();
            }
        }

        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
        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));
        }

        void _timer_Tick(object sender, EventArgs e)
        {
            _timer.Stop();

            RunTest();

            _timer.Start();
        }
    }
}



注2:我使用的代码从第一回答和我的记忆变得非常缓慢。请注意,1ms的比我的例子更慢,少重复。你必须让它运行几分钟你开始注意到增长之前。 5分钟后它在从30MB的起始点46MB。

NOTE2: I used the code from the first answer and my memory grew very slowly. Note that 1ms is much slower and less iterations than my example. You have to let it run for a couple minutes before you start to notice growth. After 5 minutes it's at 46MB from a starting point of 30MB.

注3:删除通话完全.Arrange消除了增长。不幸的是,这一呼吁是因为在许多情况下,我创建从画布PNG文件(通过RenderTargetBitmap类)非常重要的我的使用。如果没有呼叫.Arrange它不布局画布都没有。

NOTE3: Removing the call to .Arrange completely eliminates the growth. Unfortunately, that call is pretty vital to my use since in many cases I'm creating PNG files from the Canvas (via the RenderTargetBitmap class). Without the call to .Arrange it doesn't layout the canvas at all.

推荐答案

我能够使用重现您的问题你的代码提供。内存持续增长,因为画布对象不会释放;一个内存分析器表明Dispatcher的ContextLayoutManager是坚持他们的所有(这样可以在必要时调用OnRenderSizeChanged)。

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()

BuildCanvas 的结束。

不过,请注意,画布的UIElement ;它应该在用户界面中使用。它不是旨在用作任意绘图表面。正如其他评论者已经指出,数以千计的画布对象的创建可能表明设计缺陷。我知道你的产品代码可能比较复杂,但如果它只是一个画油画,GDI +的代码上简单的形状(即System.Drawing中的类)可能更合适。

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.

这篇关于简单的WPF示例导致不受控制的内存增长的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆