Frage Speicherverlust bei Verwendung von SharedResourceDictionary


wenn Sie an einigen größeren WPF-Anwendungen gearbeitet haben, mit denen Sie vertraut sein könnten Dies. Da ResourceDictionaries immer instanziiert werden, können wir jedes Mal, wenn sie in einem XAML gefunden werden, ein Ressourcenwörterbuch mehrfach im Speicher haben. Die oben erwähnte Lösung scheint also eine sehr gute Alternative zu sein. Tatsächlich hat dieser Trick für unser aktuelles Projekt viel getan ... Speicherverbrauch von 800mb bis zu 44mb, was eine wirklich große Auswirkung ist. Leider hat diese Lösung ihren Preis, den ich hier zeigen möchte, und hoffentlich finde ich einen Weg, es zu vermeiden, während ich noch den SharedResourceDictionary.

Ich machte ein kleines Beispiel, um das Problem mit einem gemeinsamen Ressourcenwörterbuch zu visualisieren.

Erstellen Sie einfach eine einfache WPF-Anwendung. Fügen Sie eine Ressource Xaml hinzu

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>

Fügen Sie jetzt ein UserControl hinzu. Der Codebehind ist nur der Standard, also zeige ich nur das 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>

Der Fenstercode dahinter sieht ungefähr so ​​aus

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);
    }

Und das Fenster gefällt mir

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>

Ich weiß, dass das Programm nicht perfekt ist und wahrscheinlich einfacher gemacht werden könnte, aber während ich einen Weg finde, das Problem zu zeigen, ist dies das, was ich entwickelt habe. Sowieso:

Starten Sie dies und Sie überprüfen den Speicherverbrauch, wenn Sie einen Speicherprofiler haben, wird dies viel einfacher. Fügen Sie hinzu (indem Sie auf den Tab klicken) und entfernen Sie eine Seite und Sie werden sehen, dass alles gut funktioniert. Nichts leckt. Jetzt in der UserControl.Resources Abschnitt verwenden Sie die SharedResourceDictionary anstatt der ResourceDictionary das einschließen Shared.xaml. Du wirst sehen, dass die MyUserControl wird nach dem Entfernen einer Seite im Speicher gehalten, und der MyUserControl drin.

Ich dachte, das passiert mit allem, was über XAML wie Konverter, Benutzersteuerungen usw. instanziiert wird. Seltsamerweise passiert dies nicht mit benutzerdefinierten Steuerelementen. Meine Vermutung ist, weil nichts wirklich in benutzerdefinierten Steuerelementen, Datenvorlagen und so weiter instanziiert wird.

Also zuerst, wie können wir das vermeiden? In unserem Fall ist die Verwendung von SharedResourceDictionary ein Muss, aber die Speicherlecks machen eine produktive Nutzung unmöglich. Das Leak kann mit CustomControls anstelle von UserControls vermieden werden, was nicht immer praktisch ist. Warum werden UserControls dann stark von einem ResourceDictionary referenziert? Ich frage mich, warum niemand das vorher erlebt hat, wie ich in einer älteren Frage gesagt habe, es scheint, als ob wir Ressourcenwörterbücher und XAML absolut falsch benutzen, sonst frage ich mich, warum sie so ineffizient sind.

Ich hoffe, dass jemand etwas Licht in diese Angelegenheit bringen kann.

Danke im Voraus Nico


10
2017-07-28 10:16


Ursprung


Antworten:


Ich bin mir nicht sicher, ob das Ihr Problem lösen wird. Aber ich hatte ähnliche Probleme mit ResourceDictionary Referenzieren Kontrollen und es mit faul zu tun Hydratation. Hier ist ein Post darauf. Und dieser Code löste meine Probleme:

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);
    }
}

5
2017-07-28 10:44



Ich stoße auf das gleiche Problem, dass ich freigegebene Ressourcenverzeichnisse in einem großen WPF-Projekt benötige. Beim Lesen des Quellartikels und der Kommentare habe ich, wie in den Kommentaren vorgeschlagen, einige Korrekturen an der SharedDirectory-Klasse vorgenommen, die anscheinend die starke Referenz (in _sourceUri gespeichert) entfernt haben und den Designer korrekt arbeiten lassen. Ich habe Ihr Beispiel getestet und es funktioniert, sowohl im Designer als auch im MemProfiler, dass keine Referenzen festgehalten werden. Ich würde gerne wissen, ob jemand es verbessert hat, aber das ist es, womit ich vorankomme:

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;
        }
    } 
}

9
2017-11-20 23:32