积极答复者
wpf控件引用问题,内容被加载之后不会被释放!

问题
-
我有一个基类,继承了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错误了
答案
-
通过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没有释放造成ImageBrush和BitmapImage没有释放:
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(); }
不过这样之后,Byte和ImageBrush无法释放的问题是解决了, 可是由于你的代码中还有一些UpdateView的部分,这个写内容需要 Pages和Tools集合。他们会有些小问题,我相信你可以解决的。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 做一下处理的话,内存能够维持在 90几M(无图片),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
- 已标记为答案 Jie BaoModerator 2012年2月6日 5:01
全部回复
-
我只看到了你的添加组件的代码,没有移除组件的代码。 请问你是怎么移除的。
还有,.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
-
移除部分代码:
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);
-
无论是什么模式,一定要保证你可以对每一个创建出来的组件有足够的生命周期检测,确保它诞生时有多少对象对他有引用,再要销毁它的时候,确保这些引用都已经断开. 强制调用几次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
-
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秒执行一次GCDispatcherTimer 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
-
好吧,现在做很多无用的猜测难以确定问题,这样吧,如果你会用Windbg调试的话,最好看一下你的managed heap情况。如果不会的话,你可以尝试把你的项目做一个简单的复制,能重现这个问题的,发给我,我来帮你调试。 www.skydrive.com 就可以分享文件
Bob Bao [MSFT]
MSDN Community Support | Feedback to us
-
-
-
通过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没有释放造成ImageBrush和BitmapImage没有释放:
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(); }
不过这样之后,Byte和ImageBrush无法释放的问题是解决了, 可是由于你的代码中还有一些UpdateView的部分,这个写内容需要 Pages和Tools集合。他们会有些小问题,我相信你可以解决的。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 做一下处理的话,内存能够维持在 90几M(无图片),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
- 已标记为答案 Jie BaoModerator 2012年2月6日 5:01