Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Border.Effect binding leak memory but Border.Background does not

Tags:

c#

wpf

Using updated .NET 4.0, I found a strange memory leak that can be reproduced by following sample code.

  • app.xml has some application wide resources that bind to properties in app.xml.cs. The resource that create leak is a <DropShadowEffect> whose Color dependency property is bound to a property in App object.
  • The main window has a button to launch a LeakWindow in which resources defined in app.xml are used.
  • When the leak window is closed, it's not garbage collected. This only happen if the leak window use above mentioned DropShadowEffect resource. Does not happen on a SolidColorBrush whose Color is also bound to the same source.

Really appreciate if someone can tell me why this leak happens on the DropShadowEffect but not on the SolidColorBrush?

App.xml

<Application x:Class="WpfSimple.App"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Application.Resources>
        <!--this one make GC unable to collect LeakWindow-->
        <DropShadowEffect x:Key="AppDropShadowColor" 
              Color="{Binding Source={x:Static Application.Current}, Path=DropShadowColor, Mode=OneWay}" />
        <!--this one does not leak-->
        <SolidColorBrush x:Key="AppBackground"
              Color="{Binding Source={x:Static Application.Current}, Path=DropShadowColor, Mode=OneWay}" />
    </Application.Resources>
</Application>

App.xml.cs start MainWindow and implements INotifyPropertyChanged for the property DropShadowColor.

 public partial class App : Application, INotifyPropertyChanged
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);        
            // start main window
            var mainWindow = new MainWindow();
            mainWindow.Show();
        }

        private Color _dropShadowColor = Colors.Blue;    
        public Color DropShadowColor
        {
            get { return _dropShadowColor; }    
            set {
                _dropShadowColor = value;
                OnPropertyChanged("DropShadowColor");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) { handler(this, new PropertyChangedEventArgs(propertyName)); }
        }
    }

MainWindow.xml and MainWindow.xml.cs has a button to create a LeakWindow, shown below.

var win = new LeakWindow {Owner = this};
win.Show();

There is also another button to do GC.Collect();

LeakWindow.xml

<Window x:Class="WpfSimple.LeakWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Leak" Height="300" Width="300">
  <Grid>
    <!--leak-->
    <Border Width="200" Height="200" BorderThickness="1" BorderBrush="Black" Effect="{StaticResource AppDropShadowColor}"/>

    <!--no leak if comment out above and uncomment below-->
    <!--<Border  Width="200" Height="200" BorderThickness="1" BorderBrush="Black" Background="{StaticResource AppBackground}"/>-->
  </Grid>
</Window>

LeakWindow.xml.cs

 public partial class LeakWindow : Window
    {
        public LeakWindow()
        {
            InitializeComponent();
        }    

        ~LeakWindow()
        {
            Debug.WriteLine("LeakWindow finalized");
        }
    }

Update

  • My search show this might be related to DynamicResource\StaticResource cause memory leaks. But that one is patched early in .NET 3.5(kb967328). Also tried the WalkDictionary method mentioned in that thread but no help.
  • Change binding mode to OneTime does not help either.
  • Switch to .NET 4.5 (also patched to date) and using DynamicResource does not help.

Further investigation shows the leak is caused by a EventHandler reference from DropShadowEffect to Border.Effect. Probably a change notification due to the binding in DropShadowEffect.
Still, what weird is Why this only occurs on Border.Effect but not on Border.Background?

Workground
Adding x:Shared=false to <DropShadowEffect> in app.xml can workaround this. I can now have application--wide defined resources but lose the memory efficiency.

like image 877
Peter Avatar asked Jul 30 '15 22:07

Peter


1 Answers

I think the problem is caused by the way DropShadowEffect is attached to the visual tree. Moving the DropShadowEffect into your control templates instead of having it as a resource might solve the leak, but then you would lose having that shared resource...

like image 87
Glen Thomas Avatar answered Oct 05 '22 23:10

Glen Thomas