none
wpf控件引用问题,内容被加载之后不会被释放! RRS feed

  • 问题

  • 我有一个基类,继承了ContentControl,代码如下:

     

        public partial class Block : ContentControl
        {
            DependencyPropertyDescriptor dpd;
            bool lbdowned;
            Point lbOPoint;
            Page mPage;
    
            static Block()
            {
                Application.Current.Resources.MergedDictionaries.Add(new ResourceDictionary
                {
                    Source = new Uri("/" + Common.AssemblyName + ";component/Themes/Block.xaml", UriKind.Relative)//这个文件里面只有一个style样式,没有任何东西
                });
            }
    
            public Block()
            {
                this.Focusable = true;
                this.Background = DefaultBackground;
                this.BorderBrush = new SolidColorBrush(Colors.Black);
                this.BorderThickness = new Thickness(1);
    
                this.Loaded += new RoutedEventHandler(Block_Loaded);
                this.Unloaded += new RoutedEventHandler(Block_Unloaded);
                this.dpd = DependencyPropertyDescriptor.FromProperty(PhotoBox.BackgroundProperty, typeof(Block));
                this.dpd.AddValueChanged(this, this.OnBackgroundChanged);            
            }
    }
    

     

    然后,我有一个PhotoBox,继承了Block,代码:

    xaml:

     

    <IN:Block x:Class="Yeylol.Album.Controls.PhotoBox"
                 xmlns:IN="clr-namespace:Yeylol.Album.Controls"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                 Width="160" Height="160"
                 mc:Ignorable="d" d:DesignHeight="160" d:DesignWidth="160"
              DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}">
    
        <Grid>
            <Label Width="{Binding Width}" Height="{Binding Height}" Background="{Binding Background}"
                   BorderBrush="{Binding BorderBrush}" BorderThickness="{Binding BorderThickness}"
                   Padding="0" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
                <Image Source="{Binding Photo}" />
            </Label>
            <Label Name="lab" HorizontalAlignment="Center" VerticalAlignment="Center" Content="{Binding Description}" />
        </Grid>
    </IN:Block>

     

    Cs:

     

    public partial class PhotoBox : Block
        {
            public PhotoBox()
            {
                InitializeComponent();
    
                this.Unloaded += new RoutedEventHandler(PhotoBox_Unloaded);
            }
    
            void PhotoBox_Unloaded(object sender, RoutedEventArgs e)
            {
                this.Photo = null;
                this.Content = null;
            }
    
            #region 属性
    
            /// <summary>
            /// 获取与设置最小相生宽度
            /// </summary>
            public double MinPhotoWidth { get; set; }
    
            /// <summary>
            /// 获取与设置最小相生宽度(厘米)
            /// </summary>
            public double MinPhotoWidthCm
            {
                get
                {
                    return Common.PixelToCm(this.MinPhotoWidth);
                }
                set
                {
                    this.MinPhotoWidth = Common.CmToPixel(value);
                }
            }
    
            /// <summary>
            /// 获取与设置最小相生高度
            /// </summary>
            public double MinPhotoHeight { get; set; }
    
            /// <summary>
            /// 获取与设置最小相生高度(厘米)
            /// </summary>
            public double MinPhotoHeightCm
            {
                get
                {
                    return Common.PixelToCm(this.MinPhotoHeight);
                }
                set
                {
                    this.MinPhotoHeight = Common.CmToPixel(value);
                }
            }
    
            #endregion
    
            #region Photo Property
    
            public object Photo
            {
                get { return GetValue(PhotoProperty) as ImageSource; }
                set { SetValue(PhotoProperty, value); }
            }
    
            public static readonly DependencyProperty PhotoProperty = DependencyProperty.Register(
                "Photo",
                typeof(object),
                typeof(PhotoBox),
                new PropertyMetadata(null, PhotoChanged));
    
            static void PhotoChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                var eb = (PhotoBox)d;
                eb.lab.Visibility = e.NewValue == null ? Visibility.Visible : Visibility.Hidden;
            }
    
            #endregion
        }
    然后我在别的项目里面调用
                Yeylol.Album.Files.Page fp = page.albumPage;
                var cp = new Yeylol.Album.Controls.Page();//这个是自定义的Page页,继承Canvas
                cp.Tag = fp;
                cp.Width = fp.Width;
                cp.Height = fp.Height;
                cp.PageIndex = fp.PageIndex;
                if (fp.Background != null)
                {
                    cp.Background = new ImageBrush(Common.GetImageSource(fp.Background, (int)fp.Width, (int)fp.Height) as BitmapImage);
                }
                for (int i = 0, len = fp.Blocks.Count; i < len; i++)
                {
                    var fb = fp.Blocks[i];
                    BlockDataEntity bde = page.GetBlockData(i);
                    Yeylol.Album.Controls.Block cb = null;//这个就是调用PhotoBox
                   
                        cb = new Yeylol.Album.Controls.PhotoBox();
                        cb.Description = fb.Description;
                        var fpb = fb as Yeylol.Album.Files.PhotoBox;
                        var cpb = cb as Yeylol.Album.Controls.PhotoBox;
                        cpb.MinPhotoWidth = fpb.MinPhotoWidth;
                        cpb.MinPhotoHeight = fpb.MinPhotoHeight;
                        if (fpb.Background != null)
                        {
                            cpb.Background = new ImageBrush(Common.GetImageSource(fpb.Background, (int)fpb.Width, (int)fpb.Height) as BitmapImage);
                        }
                        if (bde != null)
                        {
                            if (bde.data != null)
                                cpb.Photo = Common.GetImageSource(Common.AbsPath + bde.data.ToString(), (int)fpb.Width, (int)fpb.Height) as BitmapImage;
                        }                
                    cb.Tag = new List<object>() { fp, null, i };
                    cb.Top = fb.Top;
                    cb.Left = fb.Left;
                    cb.Width = fb.Width;
                    cb.Height = fb.Height;
                    cp.Children.Add(cb);
                }
    我把上面的cp一添加到页面上显示,然后就一直占用内存,我用的是page。每加载一次这个东西,内存就增加几十到上百M,刷多几次,就报out of memory错误了
    2012年1月29日 6:50

答案

  • 通过debugger观察,托管堆中绝大多数是  661548c4      349     50397488 System.Byte[]   Byte[]数组占据了

    我取了其中一个,是png图像数据:

    0:005> !do 0e6e10c0

    Name:        System.Byte[]

    MethodTable: 661548c4

    EEClass:     65e8af0c

    Size:        1048588(0x10000c) bytes

    Array:       Rank 1, Number of elements 1048576, Type Byte

    Element Type:System.Byte

    Content:     .PNG........IHDR.......@.......W.....tEXtSoftware.Adobe ImageReadyq.e<... iTXtXML:com.adobe.xmp.....<?xpacket begin="..." id="W5

    Fields: None

    根据GCRoot 查到,是因为Page没有释放造成ImageBrushBitmapImage没有释放:

    DOMAIN(00784A70):HANDLE(Strong):111130:Root:  026cdc2c(System.Windows.Media.StreamAsIStream)->

      0263eb6c(System.IO.MemoryStream)->

      0f0a1480(System.Byte[])

    DOMAIN(00784A70):HANDLE(Pinned):1113e4:Root:  035493f0(System.Object[])->

     02640a5c(System.Collections.Generic.Dictionary`2[[System.Object, mscorlib],[System.ComponentModel.DependencyPropertyDescriptor, WindowsBase]])->

      02640b50(System.Collections.Generic.Dictionary`2+Entry[[System.Object, mscorlib],[System.ComponentModel.DependencyPropertyDescriptor, WindowsBase]][])->

      02680418(System.ComponentModel.DependencyPropertyDescriptor)->

      0267773c(MS.Internal.ComponentModel.DependencyObjectPropertyDescriptor)->

      0267a520(System.Collections.Generic.Dictionary`2[[System.Windows.DependencyObject, WindowsBase],[MS.Internal.ComponentModel.PropertyChangeTracker, WindowsBase]])->

      026b6538(System.Collections.Generic.Dictionary`2+Entry[[System.Windows.DependencyObject, WindowsBase],[MS.Internal.ComponentModel.PropertyChangeTracker, WindowsBase]][])->

      026cd488(Yeylol.Album.Controls.Page)->

      026cda68(System.Windows.EffectiveValueEntry[])->

      026cdf88(System.Windows.Media.ImageBrush)->

      026cdfb0(System.Windows.EffectiveValueEntry[])->

      026cdb04(System.Windows.Media.Imaging.BitmapImage)->

      0263eb6c(System.IO.MemoryStream)->

      0f0a1480(System.Byte[])

     

    所以我们要做的只是要保证你在Clear Pages之后释放了所有的Page对象和和他关联的Block对象。

     

    我的修改是:

        void Close()
        {
          this.Album = null;
          this.Tools.Clear();
          this.Tools = null; 
    
          foreach (Yeylol.Album.Controls.Page page in this.Pages)
          {
            page.Children.Clear();
            page.Background = null;
          }
          this.Pages.Clear();
          this.Pages = null; 
    
          GC.Collect();
          GC.WaitForPendingFinalizers();
          GC.Collect();
        }
    
    

    不过这样之后,ByteImageBrush无法释放的问题是解决了, 可是由于你的代码中还有一些UpdateView的部分,这个写内容需要 PagesTools集合。他们会有些小问题,我相信你可以解决的。

     

    P.S. 我还在Files.Page Files.Album上实现了析构方法:

            ~Page()
            {
              if (this.Background!=null)
              this.Background.Dispose();
              this.Background = null;
              if (this.Blocks != null) this.Blocks.Clear();
              this.Blocks = null;
              this.Album = null;
            } 
    
            ~Album()
            {
              if (this.Pages != null)
                this.Pages.Clear();
              this.Pages = null;
            }
    
    

     

    然后如果你对BitmapImage 做一下处理的话,内存能够维持在 90M(无图片),120-130M(加载了页面):

            if (fp.Background != null)
            {
              var bmp = new BitmapImage();
              bmp.CreateOptions = BitmapCreateOptions.None;
              bmp.CacheOption = BitmapCacheOption.OnLoad;
              bmp.BeginInit();
              bmp.StreamSource = fp.Background;
              bmp.EndInit();
              bmp.Freeze();
              cp.Background = new ImageBrush(bmp);
            }
            ……
    
    

     

     


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    2012年2月1日 10:26
    版主

全部回复

  • 我只看到了你的添加组件的代码,没有移除组件的代码。 请问你是怎么移除的。

    还有,.Net中,事件的关联是强引用,所以我建议你在 你的事件处理方法中,如果事件只是执行一次,请及时移除,例如你的Block的Block_Unloaded方法:

      void Block_Unloaded(object sender, RoutedEventArgs e)
      {
        this.Loaded -= new RoutedEventHandler(Block_Loaded);
        this.Unloaded -= new RoutedEventHandler(Block_Unloaded);
        ...
      }
    

     

    还有你的PictureBox也应该如此。

    最后你需要在你把组件从cp.Children中移掉后,设置移出来的对象为null,并且调用一次GC.Collect()。

    Sincerely,


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    2012年1月30日 9:35
    版主
  • 移除部分代码:
    Yeylol.Album.Controls.PhotoBox pb = ui as Yeylol.Album.Controls.PhotoBox;
                        pb.Background = null;
    
                        if (pb.Photo != null)
                        {
                            BitmapImage bi = pb.Photo as BitmapImage;
                            bi.UriSource = null;
                            bi = null;
                        }
    
                        pb.Photo = null;
                        pb.Content = null;
                        cp.Children.Remove(pb);
                        pb = null;
    

    cp.Children.Clear();
                cp.Background = null;
                cp = null;
                gridPanel.Children.Remove(cp);


    2012年1月31日 2:20
  • 我是用mvvm模式,数据源是一组控件,绑定的时候,是直接把cp控件绑定到ContentControl,发觉wpf存在缓存问题,但是又不知道什么时候清除缓存
    • 已编辑 ge.lee 2012年1月31日 2:23
    2012年1月31日 2:23
  • 无论是什么模式,一定要保证你可以对每一个创建出来的组件有足够的生命周期检测,确保它诞生时有多少对象对他有引用,再要销毁它的时候,确保这些引用都已经断开.  强制调用几次GC.Collect()

    然后还有一个建议,把你里面关于创建BitmapImage的那几行都注释掉,再测试。 检查是不是因为BitmapImage引起。 然后看这篇文章关于 BitmapImage 的内容和建议:http://blogs.msdn.com/b/jgoldb/archive/2008/02/04/finding-memory-leaks-in-wpf-based-applications.aspx


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    2012年1月31日 2:31
    版主
  •                 BitmapImage bi = new BitmapImage();
                    bi.BeginInit();
                    bi.DecodePixelWidth = width;
                    bi.DecodePixelHeight = height;
                    bi.CacheOption = BitmapCacheOption.OnLoad;
                    bi.UriSource = new Uri(path);
                    bi.EndInit();
    这个是创建Bitmapimage的,我现在是1秒执行一次GC
                DispatcherTimer dt = new DispatcherTimer();
                dt.Interval = TimeSpan.FromMilliseconds(1000);
                dt.Tick += new EventHandler(dt_Tick);
                dt.Start();
    static void dt_Tick(object sender, EventArgs e)
            {
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
            }
    我该清除的都清除了
    • 已编辑 ge.lee 2012年1月31日 2:56
    2012年1月31日 2:56
  • 甚至,我在Page , unload的时候

     

    this.Page.Content = null;

    this.Page.DataContext= null;

     

    内存还是一直存在

    如果这样都无法断开关联,那我还真不知道如何清除了
    • 已编辑 ge.lee 2012年1月31日 2:59
    2012年1月31日 2:58
  • 好吧,现在做很多无用的猜测难以确定问题,这样吧,如果你会用Windbg调试的话,最好看一下你的managed heap情况。如果不会的话,你可以尝试把你的项目做一个简单的复制,能重现这个问题的,发给我,我来帮你调试。 www.skydrive.com 就可以分享文件
    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    2012年1月31日 5:00
    版主
  • https://skydrive.live.com/redir.aspx?cid=29f92327844d6443&resid=29F92327844D6443!115&parid=29F92327844D6443!109&authkey=!AFqem9G_jKjhDLo

    这个是共享项目


    • 已编辑 ge.lee 2012年1月31日 8:59
    2012年1月31日 8:58
  • 你把这个文件放在了私有文件夹下 我访问不到. 请放在公共文件夹下面.
    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    2012年1月31日 9:44
    版主
  • 或者发我邮箱 v-bobbao at microsoft.com


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    2012年1月31日 15:27
    版主
  • 邮件已经发送,请注意查收!
    2012年2月1日 6:05
  • 通过debugger观察,托管堆中绝大多数是  661548c4      349     50397488 System.Byte[]   Byte[]数组占据了

    我取了其中一个,是png图像数据:

    0:005> !do 0e6e10c0

    Name:        System.Byte[]

    MethodTable: 661548c4

    EEClass:     65e8af0c

    Size:        1048588(0x10000c) bytes

    Array:       Rank 1, Number of elements 1048576, Type Byte

    Element Type:System.Byte

    Content:     .PNG........IHDR.......@.......W.....tEXtSoftware.Adobe ImageReadyq.e<... iTXtXML:com.adobe.xmp.....<?xpacket begin="..." id="W5

    Fields: None

    根据GCRoot 查到,是因为Page没有释放造成ImageBrushBitmapImage没有释放:

    DOMAIN(00784A70):HANDLE(Strong):111130:Root:  026cdc2c(System.Windows.Media.StreamAsIStream)->

      0263eb6c(System.IO.MemoryStream)->

      0f0a1480(System.Byte[])

    DOMAIN(00784A70):HANDLE(Pinned):1113e4:Root:  035493f0(System.Object[])->

     02640a5c(System.Collections.Generic.Dictionary`2[[System.Object, mscorlib],[System.ComponentModel.DependencyPropertyDescriptor, WindowsBase]])->

      02640b50(System.Collections.Generic.Dictionary`2+Entry[[System.Object, mscorlib],[System.ComponentModel.DependencyPropertyDescriptor, WindowsBase]][])->

      02680418(System.ComponentModel.DependencyPropertyDescriptor)->

      0267773c(MS.Internal.ComponentModel.DependencyObjectPropertyDescriptor)->

      0267a520(System.Collections.Generic.Dictionary`2[[System.Windows.DependencyObject, WindowsBase],[MS.Internal.ComponentModel.PropertyChangeTracker, WindowsBase]])->

      026b6538(System.Collections.Generic.Dictionary`2+Entry[[System.Windows.DependencyObject, WindowsBase],[MS.Internal.ComponentModel.PropertyChangeTracker, WindowsBase]][])->

      026cd488(Yeylol.Album.Controls.Page)->

      026cda68(System.Windows.EffectiveValueEntry[])->

      026cdf88(System.Windows.Media.ImageBrush)->

      026cdfb0(System.Windows.EffectiveValueEntry[])->

      026cdb04(System.Windows.Media.Imaging.BitmapImage)->

      0263eb6c(System.IO.MemoryStream)->

      0f0a1480(System.Byte[])

     

    所以我们要做的只是要保证你在Clear Pages之后释放了所有的Page对象和和他关联的Block对象。

     

    我的修改是:

        void Close()
        {
          this.Album = null;
          this.Tools.Clear();
          this.Tools = null; 
    
          foreach (Yeylol.Album.Controls.Page page in this.Pages)
          {
            page.Children.Clear();
            page.Background = null;
          }
          this.Pages.Clear();
          this.Pages = null; 
    
          GC.Collect();
          GC.WaitForPendingFinalizers();
          GC.Collect();
        }
    
    

    不过这样之后,ByteImageBrush无法释放的问题是解决了, 可是由于你的代码中还有一些UpdateView的部分,这个写内容需要 PagesTools集合。他们会有些小问题,我相信你可以解决的。

     

    P.S. 我还在Files.Page Files.Album上实现了析构方法:

            ~Page()
            {
              if (this.Background!=null)
              this.Background.Dispose();
              this.Background = null;
              if (this.Blocks != null) this.Blocks.Clear();
              this.Blocks = null;
              this.Album = null;
            } 
    
            ~Album()
            {
              if (this.Pages != null)
                this.Pages.Clear();
              this.Pages = null;
            }
    
    

     

    然后如果你对BitmapImage 做一下处理的话,内存能够维持在 90M(无图片),120-130M(加载了页面):

            if (fp.Background != null)
            {
              var bmp = new BitmapImage();
              bmp.CreateOptions = BitmapCreateOptions.None;
              bmp.CacheOption = BitmapCacheOption.OnLoad;
              bmp.BeginInit();
              bmp.StreamSource = fp.Background;
              bmp.EndInit();
              bmp.Freeze();
              cp.Background = new ImageBrush(bmp);
            }
            ……
    
    

     

     


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    2012年2月1日 10:26
    版主