如果你在一些较大的wpf应用程序上工作,你可能熟悉this。因为ResourceDictionaries总是被实例化,所以每次在XAML中找到它们时,我们最终可能会在内存中多次使用一个资源字典。所以上面提到的解决方案似乎是一个非常好的选择。事实上,对于我们目前的项目,这个技巧做了很多......内存消耗从800mb减少到44mb,这是一个非常巨大的影响。不幸的是,这个解决方案是有代价的,我想在这里展示,并希望找到一种方法来避免它,同时仍然使用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>
现在添加一个UserControl。代码隐藏只是默认值,所以我只显示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>
后面的Window代码看起来像这样
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>
我知道这个程序并不完美,而且可以让它变得更容易,但在找出解决问题的方法时,这就是我想出来的。无论如何:
启动它并检查内存消耗,如果你有一个内存分析器,这会变得更容易。添加(通过单击选项卡显示)并删除页面,您将看到一切正常。什么都没有泄漏。
现在在UserControl.Resources
部分中,使用SharedResourceDictionary
代替ResourceDictionary
来包含 Shared.xaml 。移除页面后,您会看到MyUserControl
将保留在内存中,并且MyUserControl
位于内存中。
我认为这种情况发生在通过XAML实例化的所有内容中,例如转换器,用户控件等。奇怪的是,这不会发生在自定义控件上。我的猜测是,因为在自定义控件,数据模板等方面没有真正实例化。
首先我们如何避免这种情况?在我们的情况下,使用SharedResourceDictionary是必须的,但内存泄漏使得无法有效地使用它。 使用CustomControls而不是UserControls可以避免泄漏,这实际上并不总是如此。那么为什么UserControls被ResourceDictionary强引用? 我想知道为什么以前没有人经历过这个,就像我在一个较旧的问题中说的那样,我们似乎使用资源词典和XAML绝对错误,否则我想知道为什么它们如此无能为力。
我希望有人可以对这件事有所了解。
提前致谢 尼科
答案 0 :(得分:12)
我遇到了在大型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;
}
}
}
答案 1 :(得分:7)
我不太确定这是否能解决您的问题。但是我对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);
}
}