2011-07-28 105 views
10

时,如果你的工作在一些较大的WPF应用程序,你可能熟悉this。因为ResourceDictionaries总是被实例化,所以每次在XAML中发现它们时,我们最终都会在内存中多次使用一个资源字典。所以上述解决方案似乎是一个非常好的选择。实际上,对于我们目前的项目来说,这个技巧做了很多...内存消耗从800mb降至44mb,这是一个非常大的影响。不幸的是,这个解决方案是有代价的,我想在这里展示,并希望找到一种方法来避免它,同时仍然使用SharedResourceDictionary内存泄漏使用SharedResourceDictionary

我做了一个小例子与共享资源字典来可视化的问题。

只需创建一个简单的WPF应用程序。添加一个资源的XAML

Shared.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> 

    <SolidColorBrush x:Key="myBrush" Color="Yellow"/> 

</ResourceDictionary> 

现在添加一个用户控件。代码隐藏只是默认的,所以我只是显示XAML

MyUserControl.xaml

<UserControl x:Class="Leak.MyUserControl" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      xmlns:SharedResourceDictionary="clr-namespace:Articy.SharedResourceDictionary" Height="128" Width="128"> 

    <UserControl.Resources> 
     <ResourceDictionary> 
      <ResourceDictionary.MergedDictionaries> 
       <ResourceDictionary Source="/Leak;component/Shared.xaml"/> 
      </ResourceDictionary.MergedDictionaries> 
     </ResourceDictionary> 
    </UserControl.Resources> 

    <Grid> 
     <Rectangle Fill="{StaticResource myBrush}"/>  
    </Grid> 
</UserControl> 

的窗口后面的代码看起来是这样的

Window1.xaml.cs

// [ ... ] 
    public Window1() 
    { 
     InitializeComponent(); 
     myTabs.ItemsSource = mItems; 
    } 

    private ObservableCollection<string> mItems = new ObservableCollection<string>(); 

    private void OnAdd(object aSender, RoutedEventArgs aE) 
    { 
     mItems.Add("Test"); 
    } 
    private void OnRemove(object aSender, RoutedEventArgs aE) 
    { 
     mItems.RemoveAt(mItems.Count - 1); 
    } 

和窗口XAML这样

Window1.xaml

<Window.Resources> 
     <DataTemplate x:Key="myTemplate" DataType="{x:Type System:String}"> 
      <Leak:MyUserControl/> 
     </DataTemplate> 
    </Window.Resources> 

    <Grid> 
     <DockPanel> 
      <StackPanel DockPanel.Dock="Top" Orientation="Horizontal"> 
       <Button Content="Add" Click="OnAdd"/> 
       <Button Content="Remove" Click="OnRemove"/> 
      </StackPanel> 
      <TabControl x:Name="myTabs" ContentTemplate="{StaticResource myTemplate}"> 
      </TabControl> 
     </DockPanel> 
    </Grid> 
</Window> 

我知道程序不健全,propably可以作出更容易,但同时想办法,借以说明问题这是我想出了。总之:

启动这一点,你检查内存消耗,如果你有一个内存分析器这变得更加容易。添加(通过点击标签显示它)并删除一个页面,你会看到一切工作正常。没有任何泄漏。 现在在UserControl.Resources部分使用SharedResourceDictionary代替ResourceDictionary到包括Shared.xaml。您将看到在删除页面后,MyUserControl将保留在内存中,其中的MyUserControl将保留在内存中。

我想这恰好是通过XAML实例像器应有尽有,用户控件等奇怪的是这不会发生在自定义控件。我的猜测是,因为没有什么是真正的自定义控件,数据模板等实例化。

那么首先我们该如何避免这种情况?在我们的例子中,使用SharedResourceDictionary是必须的,但内存泄漏使得它不可能高效地使用它。 可以使用CustomControls而不是UserControls来避免泄漏,而这并不总是实际。那么为什么UserControls被ResourceDictionary强引用? 我想知道为什么没有人经历过这种情况,就像我在一个较老的问题中所说的那样,似乎我们使用资源字典和XAML绝对是错误的,否则我想知道他们为什么如此无奈。

我希望有人能够对这件事情有所了解。

在此先感谢 尼科

+0

在你的内存设置,是什么对象保持参照的MyUserControl实例? –

+0

我记不得了,但我几乎可以确定它是一个ResourceDictionary,在我的情况下是SharedResourceDictionary。 – dowhilefor

回答

5

我不太清楚这是否会解决您的问题。但我有与ResourceDictionary引用控件类似的问题,它与懒惰hydration。这是一个post就可以了。而这种代码解决了我的问题:

public partial class App : Application 
{ 
    protected override void OnStartup(StartupEventArgs e) 
    { 
     WalkDictionary(this.Resources); 

     base.OnStartup(e); 
    } 

    private static void WalkDictionary(ResourceDictionary resources) 
    { 
     foreach (DictionaryEntry entry in resources) 
     { 
     } 

     foreach (ResourceDictionary rd in resources.MergedDictionaries) 
      WalkDictionary(rd); 
    } 
} 
+0

感谢您的链接,这是一个很好的开始找到一些见解。这已经帮助我。虽然预先遍历所有资源的实际解决方案对我们来说并不完全是一种选择,因为我们已经安静了许多资源(有几个资源被破坏)。如果没有更好的事情发生,我会将你的答案标记为最佳答案。 – dowhilefor

9

我运行到需要共享资源目录在大十岁上下的WPF项目的同样的问题。阅读源文章和评论,我按照评论中的建议,将一些修复程序并入了SharedDirectory类,这似乎删除了强参考(存储在_sourceUri中),并使设计器正常工作。我测试了你的例子,它在设计器和MemProfiler中都能成功地注意到没有持有的引用。我很想知道,如果有人有了进一步的提高,但是,这个就是我跟现在去:

public class SharedResourceDictionary : ResourceDictionary 
{ 
    /// <summary> 
    /// Internal cache of loaded dictionaries 
    /// </summary> 
    public static Dictionary<Uri, ResourceDictionary> _sharedDictionaries = 
     new Dictionary<Uri, ResourceDictionary>(); 

    /// <summary> 
    /// Local member of the source uri 
    /// </summary> 
    private Uri _sourceUri; 

    /// <summary> 
    /// Gets or sets the uniform resource identifier (URI) to load resources from. 
    /// </summary> 
    public new Uri Source 
    { 
     get { 
      if (IsInDesignMode) 
       return base.Source; 
      return _sourceUri; 
     } 
     set 
     { 
      if (IsInDesignMode) 
      { 
       try 
       { 
        _sourceUri = new Uri(value.OriginalString); 
       } 
       catch 
       { 
        // do nothing? 
       } 

       return; 
      } 

      try 
      { 
       _sourceUri = new Uri(value.OriginalString); 
      } 
      catch 
      { 
       // do nothing? 
      } 

      if (!_sharedDictionaries.ContainsKey(value)) 
      { 
       // If the dictionary is not yet loaded, load it by setting 
       // the source of the base class 

       base.Source = value; 

       // add it to the cache 
       _sharedDictionaries.Add(value, this); 
      } 
      else 
      { 
       // If the dictionary is already loaded, get it from the cache 
       MergedDictionaries.Add(_sharedDictionaries[value]); 
      } 
     } 
    } 

    private static bool IsInDesignMode 
    { 
     get 
     { 
      return (bool)DependencyPropertyDescriptor.FromProperty(DesignerProperties.IsInDesignModeProperty, 
      typeof(DependencyObject)).Metadata.DefaultValue; 
     } 
    } 
} 
+3

这种方法的一个有趣的扩展使用'WeakReference',这样'ResourceDictionary'对象可以在不使用时被垃圾回收。有关详细信息,请参见[本博客文章](https://codeblitz.wordpress.com/2010/08/25/resourcedictionary-use-with-care/)。 –

+0

德鲁,你有没有测试过,看垃圾收集是否有效? – tofutim