使用SharedResourceDictionary时内存泄漏

dow*_*for 12 wpf memory-leaks resourcedictionary

如果你在一些更大的wpf应用程序上工作,你可能会熟悉.因为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>
Run Code Online (Sandbox Code Playgroud)

现在添加一个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>
Run Code Online (Sandbox Code Playgroud)

后面的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);
    }
Run Code Online (Sandbox Code Playgroud)

窗口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>
Run Code Online (Sandbox Code Playgroud)

我知道这个程序并不完美,而且可以让它变得更容易,但在找出问题的方法时,我想出了这个问题.无论如何:

启动它并检查内存消耗,如果你有一个内存分析器,这将变得更容易.添加(通过单击选项卡显示)并删除页面,您将看到一切正常.什么都没有泄漏.现在在该UserControl.Resources部分中使用SharedResourceDictionary而不是ResourceDictionary包含Shared.xaml.MyUserControl删除页面后,您将看到将保留在内存MyUserControl中.

我认为这种情况发生在通过XAML实例化的所有内容中,例如转换器,用户控件等.奇怪的是,这不会发生在Custom控件上.我的猜测是,因为在自定义控件,数据模板等方面没有真正实例化.

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

我希望有人可以对此事进行阐述.

在此先感谢Nico

Sco*_* O. 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;
        }
    } 
}
Run Code Online (Sandbox Code Playgroud)

  • 这种方法的一个有趣的扩展使用了'WeakReference`,因此`ResourceDictionary`对象可以在不使用时进行垃圾收集.有关详细信息,请参阅[此博客文章](https://codeblitz.wordpress.com/2010/08/25/resourcedictionary-use-with-care/). (3认同)

ani*_*vas 7

我不太确定这是否能解决您的问题.但是我在ResourceDictionary引用控件方面遇到了类似的问题,而且与懒惰的水合作用有关.这是一篇帖子.这段代码解决了我的问题:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)