46 votes

De toute façon pour contourner WPF l'appel de la GC.Collecter(2) indépendamment de la réflexion?

J'ai récemment eu à vérifier dans cette monstruosité dans le code de production de manipuler les champs privés dans un WPF classe: (tl;dr comment puis-je éviter d'avoir à le faire?)

private static class MemoryPressurePatcher
{
    private static Timer gcResetTimer;
    private static Stopwatch collectionTimer;
    private static Stopwatch allocationTimer;
    private static object lockObject;

    public static void Patch()
    {
        Type memoryPressureType = typeof(Duration).Assembly.GetType("MS.Internal.MemoryPressure");
        if (memoryPressureType != null)
        {
            collectionTimer = memoryPressureType.GetField("_collectionTimer", BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null) as Stopwatch;
            allocationTimer = memoryPressureType.GetField("_allocationTimer", BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null) as Stopwatch;
            lockObject = memoryPressureType.GetField("lockObj", BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null);

            if (collectionTimer != null && allocationTimer != null && lockObject != null)
            {
                gcResetTimer = new Timer(ResetTimer);
                gcResetTimer.Change(TimeSpan.Zero, TimeSpan.FromMilliseconds(500));
            }
        }                
    }       

    private static void ResetTimer(object o)
    {
        lock (lockObject)
        {
            collectionTimer.Reset();
            allocationTimer.Reset();
        }
    }
}

Pour comprendre pourquoi je ferais quelque chose de tellement fou, vous avez besoin de regarder MS.Internal.MemoryPressure.ProcessAdd():

/// <summary>
/// Check the timers and decide if enough time has elapsed to
/// force a collection
/// </summary>
private static void ProcessAdd()
{
    bool shouldCollect = false;

    if (_totalMemory >= INITIAL_THRESHOLD)
    {
        // need to synchronize access to the timers, both for the integrity
        // of the elapsed time and to ensure they are reset and started
        // properly
        lock (lockObj)
        {
            // if it's been long enough since the last allocation
            // or too long since the last forced collection, collect
            if (_allocationTimer.ElapsedMilliseconds >= INTER_ALLOCATION_THRESHOLD
                || (_collectionTimer.ElapsedMilliseconds > MAX_TIME_BETWEEN_COLLECTIONS))
            {
                _collectionTimer.Reset();
                _collectionTimer.Start();

                shouldCollect = true;
            }
            _allocationTimer.Reset();
            _allocationTimer.Start();
        }

        // now that we're out of the lock do the collection
        if (shouldCollect)
        {
            Collect();
        }
    }

    return;
}

L'important est près de l'extrémité, où il appelle la méthode Collect():

private static void Collect()
{
    // for now only force Gen 2 GCs to ensure we clean up memory
    // These will be forced infrequently and the memory we're tracking
    // is very long lived so it's ok
    GC.Collect(2);
}

Oui, c'est WPF forcer un gen 2 la collecte des ordures, où les forces d'un blocage complet du GC. Naturellement GC arrive sans blocage sur la gen 2 tas. Ce que cela signifie en pratique que lorsque cette méthode est appelée, l'ensemble de notre application se bloque. Plus la mémoire de votre application à l'aide de, et la de plus en plus fragmenté votre gen 2 tas est, plus il faudra. Notre application actuellement caches un peu de données et peut facilement prendre un gig de mémoire et le travail forcé GC permet de verrouiller notre application sur un lent appareil pendant plusieurs secondes, chaque 850 MME

Car, en dépit de l'auteur protestations du contraire, il est facile d'en arriver à un scénario où cette méthode est appelée avec une grande fréquence. Ce mémoire de code de WPF est produit lors du chargement d'un BitmapSource à partir d'un fichier. Nous virtualiser un listview avec des milliers d'articles où chaque élément est représenté par une vignette stockées sur le disque. Comme nous l'avons faites défiler vers le bas, nous sommes le chargement dynamique dans ces vignettes, et que la GC qui se passe à la fréquence maximale. Afin de défilement devient incroyablement lent et saccadé avec l'app verrouillage vers le haut en permanence.

Avec ce terrible réflexion hack je l'ai mentionné en haut, on force les compteurs à zéro pour ne jamais être atteint, et donc WPF jamais les forces de la GC. En outre, il semble y avoir pas de conséquences négatives à-dire la mémoire se développe comme l'un des parchemins et éventuellement un GC est déclenchée naturellement, sans bloquer le thread principal.

Est-il une autre option pour éviter que ces appels à l' GC.Collect(2) qui n'est pas si flagrante hideux comme ma solution? Aimerais obtenir une explication de ce que le béton sont les problèmes qui peuvent découler de la suivre à travers avec ce hack. Je veux dire par là des problèmes d'éviter l'appel à GC.Collect(2). (me semble que le GC naturelle devrait être suffisant)

11voto

Lucas Trzesniewski Points 28646

Avis: Ne faites ceci que si elle provoque un goulot d'étranglement dans votre application, et assurez-vous de comprendre les conséquences, Voir Hans réponse pour une bonne explication sur pourquoi ils ont mis cela en WPF en premier lieu.

Vous avez une certaine mauvaise code d'essayer de corriger un méchant hacker dans le cadre... Que tout est statique et appelé à partir de plusieurs endroits dans WPF, vous ne pouvez pas vraiment faire de mieux que d'utiliser la réflexion pour le casser (d'autres solutions seraient bien pire).

Ne vous attendez donc pas une propre solution. Il n'existe pas, à moins qu'ils modifier le code WPF.

Mais je pense que votre hack pourrait être plus simple et éviter d'utiliser une minuterie: il suffit de pirater le _totalMemory de la valeur et vous avez terminé. C'est un long, ce qui signifie qu'il peut aller à des valeurs négatives. Et de très grandes valeurs négatives à l'.

private static class MemoryPressurePatcher
{
    public static void Patch()
    {
        var memoryPressureType = typeof(Duration).Assembly.GetType("MS.Internal.MemoryPressure");
        var totalMemoryField = memoryPressureType?.GetField("_totalMemory", BindingFlags.Static | BindingFlags.NonPublic);

        if (totalMemoryField?.FieldType != typeof(long))
            return;

        var currentValue = (long) totalMemoryField.GetValue(null);

        if (currentValue >= 0)
            totalMemoryField.SetValue(null, currentValue + long.MinValue);
    }
}

Ici, maintenant, votre application devrait allouer environ 8 exaoctets avant d'appeler GC.Collect. Inutile de dire que, si cela se produit, vous aurez plus de problèmes à résoudre. :)

Si vous êtes inquiet au sujet de la possibilité d'un dépassement de capacité, il suffit d'utiliser long.MinValue / 2 que le décalage. Cela reste encore à vous 4 exaoctets.

Notez que AddToTotal effectue la vérification des limites de l' _totalMemory, mais il le fait avec un Debug.Assert ici:

Debug.Assert(newValue >= 0);

Comme vous serez à l'aide d'une version de l' .NET Framework, de ces affirme sera désactivée (avec un ConditionalAttribute), donc il n'y a pas besoin de s'inquiéter à ce sujet.


Vous avez demandé quels sont les problèmes qui pourraient se poser avec cette approche. Jetons un coup d'oeil.

  • Le plus évident: MME modifie le code WPF vous êtes à essayer de pirater.

    Eh bien, dans ce cas, il dépend de la nature de la modification.

    • Ils changent le nom du type/nom du champ/type de champ: dans ce cas, le hack ne sera pas effectuée, et vous serez de retour pour le stock de comportement. Le code de réflexion est assez défensive, il ne va pas faire une exception, il ne peut pas faire n'importe quoi.

    • Ils changent Debug.Assert appel à un moment de l'exécution qui est activé dans la version. Dans ce cas, votre application est vouée à l'échec. Toute tentative de charger une image à partir du disque va le jeter. Oups.

      Ce risque est atténué par le fait que leur code est à peu près un hack. Ils n'ont pas l'intention de jeter, il devrait passer inaperçu. Ils veulent lui de s'asseoir tranquille et silencieuse. Laisser les images de la charge est beaucoup plus caractéristique importante qui ne doit pas être altérée par certains code de gestion de mémoire dont le seul but est de maintenir l'utilisation de la mémoire au minimum.

    • Dans le cas de votre patch d'origine dans l'OP, si ils changent les valeurs de constante, votre hack peut cesser de fonctionner.

    • Ils modifier l'algorithme tout en gardant la classe que sur le terrain intact. Eh bien... tout peut arriver, en fonction de la modification.

  • Maintenant, supposons que le hack fonctionne et désactive l' GC.Collect appel avec succès.

    Le risque évident dans ce cas est l'accroissement de l'utilisation de la mémoire. Depuis les collections seront de moins en moins fréquentes, plus la mémoire sera allouée à un moment donné. Cela ne devrait pas être un gros problème, car les collections de toujours se produire naturellement lors de la génération 0 se remplit.

    Il faudrait aussi plus de fragmentation de la mémoire, c'est une conséquence directe de la diminution des collections. Cela peut ou peut ne pas être un problème pour vous - profil de votre application.

    Moins de collections signifie aussi moins d'objets, sont promus à une hausse de la génération. C'est une bonne chose. Idéalement, vous devriez avoir une courte durée de vie des objets dans la génération 0, et longue durée de vie des objets dans gen 2. Fréquentes de la collection en fait la cause de courte durée objets de la promotion de la gen 1, puis à la génération 2, et vous vous retrouverez avec de nombreux inaccessible objets dans gen 2. Ces informations seront uniquement être nettoyé avec un gen 2 de la collection, sera la cause de la fragmentation du segment, et augmentera la GC temps, car il faudra passer plus de temps en compactant le tas. C'est en fait la raison principale pour laquelle l'appelant GC.Collect vous-même est considéré comme une mauvaise pratique, vous êtes activement en battant la stratégie du gouvernement du canada, ce qui affecte l'ensemble de l'application.

Dans tous les cas, la bonne méthode serait de charger les images, la mise à l'échelle vers le bas et d'afficher ces vignettes dans votre INTERFACE utilisateur. L'ensemble de ce traitement doit être fait dans un thread d'arrière-plan. Dans le cas d'images JPEG, la charge de l'intégrés miniatures - ils peuvent être assez bon. Et utiliser un objet de la piscine de sorte que vous n'avez pas besoin d'instancier de nouvelles images bitmaps à chaque fois, c'ignore totalement l' MemoryPressure classe de problème. Et oui, c'est exactement ce que les autres réponses suggèrent ;)

10voto

Hans Passant Points 475940

Je pense que ce que vous avez est tout simplement parfait. Bien fait, agréable hack, la Réflexion est un outil génial pour fixer déglingués code de la structure. Je l'ai utilisé moi-même à plusieurs reprises. Juste de limiter son usage à la vue qui affiche la liste, il est trop dangereux de l'avoir actif tout le temps.

Noodling un peu plus sur le problème sous-jacent, l'horrible ProcessAdd() hack est bien sûr très brut. C'est une conséquence de BitmapSource pas la mise en œuvre de IDisposable. Une conception discutable de décision, DONC, est rempli de questions à ce sujet. Cependant, la majorité d'entre eux sont sur le problème inverse, ce minuteur de ne pas être assez rapide pour suivre le rythme. Ça ne fonctionne pas très bien.

Il n'ya rien que vous pouvez faire pour changer la façon dont ce code fonctionne. Les valeurs qu'il retire sont const déclarations. Fondée sur des valeurs que peut-être il y a 15 ans, l'âge probable de ce code. Il commence à un mégaoctet et les appels "10s de MO" un problème, la vie était plus simple à l'époque :) Ils ont oublié de l'écrire, de sorte qu'il évolue correctement, GC.AddMemoryPressure() serait probablement bien aujourd'hui. Trop peu, trop tard, ils ne peuvent pas fixer plus de cette façon, sans altérer notablement le comportement du programme.

Vous pouvez certainement la défaite de la minuterie et d'éviter que votre hack. Sûrement le problème que vous avez droit maintenant, c'est que son Intervalle est d'environ le même que le taux auquel un utilisateur fait défiler la liste quand il ne fait pas lire quoi que ce soit, mais essaie juste de trouver l'enregistrement d'intérêt. C'est un problème de conception de l'INTERFACE utilisateur qui est commun avec les vues de liste avec des milliers de lignes, une question que vous ne voulez probablement pas à l'adresse. Ce que vous devez faire est de mettre en cache les vignettes, la collecte de ceux qui vous savez que vous aurez probablement besoin de la prochaine. La meilleure façon de le faire est de le faire dans un pool de threads thread. Mesurer le temps pendant que vous faites cela, vous pouvez passer jusqu'à 850 msec. Ce code est toutefois pas être plus petit que ce que vous avez maintenant, pas beaucoup plus joli non plus.

9voto

Alois Kraus Points 6179

.NET 4.6.2 la corriger en tuant les MemoryPressure classe ensemble. J'ai juste vérifié l'aperçu et mon INTERFACE se bloque sont entièrement disparu.

.NET de 4,6 implemements il

internal SafeMILHandleMemoryPressure(long gcPressure)
{
    this._gcPressure = gcPressure;
    this._refCount = 0;
    GC.AddMemoryPressure(this._gcPressure);
}

alors que les pré .NET 4.6.2 vous avez eu ce brut MemoryPressure classe qui aurait la force d'un GC.Recueillir tous les 850ms (si entre no WPF Bitmaps ont été attribués) ou toutes les 30s, peu importe combien WPF bitmaps vous n'avez allouer.

Pour la référence à l'ancien poignée a été mis en œuvre comme

internal SafeMILHandleMemoryPressure(long gcPressure)
{
    this._gcPressure = gcPressure;
    this._refCount = 0;
    if (this._gcPressure > 8192L)
    {
        MemoryPressure.Add(this._gcPressure);   // Kills UI interactivity !!!!!
        return;
    }
    GC.AddMemoryPressure(this._gcPressure);
}

Cela fait une énorme différence comme vous pouvez le voir le GC suspension fois tomber dans une simple application de test je l'ai fait écrire à reproduire le problème. enter image description here

Vous voyez ici que la GC suspension de fois avez-déposer à partir de 2,71 s jusqu'à 0,86 s. Cette demeure à peu près constante, même pour le multi GO géré tas. Cela a également une augmentation globale des performances de l'application, car désormais l'arrière-plan GC ne peut faire son travail là où il se doit: En arrière-plan. Cela empêche soudain s'arrête de tous les threads gérés qui peuvent continuer à travailler heureux bien que le GC est de nettoyer les choses. Pas beaucoup de gens sont conscients de ce contexte GC leur donne, mais cela fait un monde réel différence de ca. De 10 à 15% pour les communes les charges de travail applicatives. Si vous avez un multi GO géré application où un full GC peut prendre quelques secondes, vous remarquerez une amélioration spectaculaire. Dans certains tests ont été une application a une fuite de mémoire (5 GO tas managé, full GC suspendre le temps, 7s), je ne vois 35s de l'INTERFACE utilisateur des retards en raison de ces forcé GCs!

6voto

cokeman19 Points 1720

Pour la mise à jour de question au sujet de ce que le béton sont les problèmes que vous pouvez rencontrer en utilisant la réflexion, je pense que @HansPassant a été approfondie dans son évaluation de votre approche spécifique. Mais de manière plus générale, le risque que vous courez avec votre approche actuelle est le même risque que vous courez avec l'aide de toute réflexion contre le code ne vous appartient pas; il peut changer en dessous de vous dans la prochaine mise à jour. Tant que vous êtes à l'aise avec cela, le code doit avoir un risque négligeable.

Nous l'espérons, de répondre à la question initiale, il y a peut être un moyen de contourner l' GC.Collect(2) problème en réduisant le nombre d' BitmapSource des opérations. Ci-dessous est un exemple d'application qui illustre bien ma pensée. Similaire à ce que vous avez décrit, il utilise un virtualisé ItemsControl pour l'affichage des vignettes à partir du disque.

Il y a peut être d'autres, le principal point d'intérêt est de savoir comment les images miniatures sont construits. L'application crée un cache de WriteableBitmap objets initiaux. Comme la liste des éléments qui sont requis par l'INTERFACE utilisateur, il lit l'image à partir du disque, à l'aide d'un BitmapFrame pour récupérer les informations de l'image, principalement des données de pixels. Un WriteableBitmap objet est tiré à partir du cache, c'est les données de pixel est écrasé, puis il est affecté à la vue-modèle. Que les éléments de la liste de tomber hors de la vue et sont recyclés, l' WriteableBitmap objet est retourné à la cache pour les réutiliser ultérieurement. La seule BitmapSourcede l'activité liée engagés au cours de l'ensemble de ce processus est le chargement de l'image à partir du disque.

Il est intéressant de noter que, afin que l'image renvoyée par l' GetBitmapImageBytes() méthode doit être exactement la même taille que ceux de l' WriteableBitmap cache pour ce pixel-remplacer approche du travail; de 256 x 256. Pour des raisons de simplicité, les images bitmap j'ai utilisé lors de mes tests étaient déjà à cette taille, mais il doit être facile à mettre en œuvre mise à l'échelle.

MainWindow.xaml:

<Window x:Class="VirtualizedListView.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="500" Width="500">
    <Grid>
        <ItemsControl VirtualizingStackPanel.IsVirtualizing="True"
                      VirtualizingStackPanel.VirtualizationMode="Recycling"
                      VirtualizingStackPanel.CleanUpVirtualizedItem="VirtualizingStackPanel_CleanUpVirtualizedItem"
                      ScrollViewer.CanContentScroll="True"
                      ItemsSource="{Binding Path=Thumbnails}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Border BorderBrush="White" BorderThickness="1">
                        <Image Source="{Binding Image, Mode=OneTime}" Height="128" Width="128" />
                    </Border>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.Template>
                <ControlTemplate>
                    <Border BorderThickness="{TemplateBinding Border.BorderThickness}"
                            Padding="{TemplateBinding Control.Padding}"
                            BorderBrush="{TemplateBinding Border.BorderBrush}"
                            Background="{TemplateBinding Panel.Background}"
                            SnapsToDevicePixels="True">
                        <ScrollViewer Padding="{TemplateBinding Control.Padding}" Focusable="False">
                            <ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
                        </ScrollViewer>
                    </Border>
                </ControlTemplate>
            </ItemsControl.Template>
        </ItemsControl>
    </Grid>
</Window>

MainWindow.xaml.cs:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;

namespace VirtualizedListView
{
    public partial class MainWindow : Window
    {
        private const string ThumbnailDirectory = @"D:\temp\thumbnails";

        private ConcurrentQueue<WriteableBitmap> _writeableBitmapCache = new ConcurrentQueue<WriteableBitmap>();

        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;

            // Load thumbnail file names
            List<string> fileList = new List<string>(System.IO.Directory.GetFiles(ThumbnailDirectory));

            // Load view-model
            Thumbnails = new ObservableCollection<Thumbnail>();
            foreach (string file in fileList)
                Thumbnails.Add(new Thumbnail(GetImageForThumbnail) { FilePath = file });

            // Create cache of pre-built WriteableBitmap objects; note that this assumes that all thumbnails
            // will be the exact same size.  This will need to be tuned for your needs
            for (int i = 0; i <= 99; ++i)
                _writeableBitmapCache.Enqueue(new WriteableBitmap(256, 256, 96d, 96d, PixelFormats.Bgr32, null));
        }

        public ObservableCollection<Thumbnail> Thumbnails
        {
            get { return (ObservableCollection<Thumbnail>)GetValue(ThumbnailsProperty); }
            set { SetValue(ThumbnailsProperty, value); }
        }
        public static readonly DependencyProperty ThumbnailsProperty =
            DependencyProperty.Register("Thumbnails", typeof(ObservableCollection<Thumbnail>), typeof(MainWindow));

        private BitmapSource GetImageForThumbnail(Thumbnail thumbnail)
        {
            // Get the thumbnail data via the proxy in the other app domain
            ImageLoaderProxyPixelData pixelData = GetBitmapImageBytes(thumbnail.FilePath);
            WriteableBitmap writeableBitmap;

            // Get a pre-built WriteableBitmap out of the cache then overwrite its pixels with the current thumbnail information.
            // This avoids the memory pressure being set in this app domain, keeping that in the app domain of the proxy.
            while (!_writeableBitmapCache.TryDequeue(out writeableBitmap)) { Thread.Sleep(1); }
            writeableBitmap.WritePixels(pixelData.Rect, pixelData.Pixels, pixelData.Stride, 0);

            return writeableBitmap;
        }

        private ImageLoaderProxyPixelData GetBitmapImageBytes(string fileName)
        {
            // All of the BitmapSource creation occurs in this method, keeping the calls to 
            // MemoryPressure.ProcessAdd() localized to this app domain

            // Load the image from file
            BitmapFrame bmpFrame = BitmapFrame.Create(new Uri(fileName));
            int stride = bmpFrame.PixelWidth * bmpFrame.Format.BitsPerPixel;
            byte[] pixels = new byte[bmpFrame.PixelHeight * stride];

            // Construct and return the image information
            bmpFrame.CopyPixels(pixels, stride, 0);
            return new ImageLoaderProxyPixelData()
            {
                Pixels = pixels,
                Stride = stride,
                Rect = new Int32Rect(0, 0, bmpFrame.PixelWidth, bmpFrame.PixelHeight)
            };
        }

        public void VirtualizingStackPanel_CleanUpVirtualizedItem(object sender, CleanUpVirtualizedItemEventArgs e)
        {
            // Get a reference to the WriteableBitmap before nullifying the property to release the reference
            Thumbnail thumbnail = (Thumbnail)e.Value;
            WriteableBitmap thumbnailImage = (WriteableBitmap)thumbnail.Image;
            thumbnail.Image = null;

            // Asynchronously add the WriteableBitmap back to the cache
            Dispatcher.BeginInvoke((Action)(() =>
            {
                _writeableBitmapCache.Enqueue(thumbnailImage);
            }), System.Windows.Threading.DispatcherPriority.Loaded);
        }
    }

    // View-Model
    public class Thumbnail : DependencyObject
    {
        private Func<Thumbnail, BitmapSource> _imageGetter;
        private BitmapSource _image;

        public Thumbnail(Func<Thumbnail, BitmapSource> imageGetter)
        {
            _imageGetter = imageGetter;
        }

        public string FilePath
        {
            get { return (string)GetValue(FilePathProperty); }
            set { SetValue(FilePathProperty, value); }
        }
        public static readonly DependencyProperty FilePathProperty =
            DependencyProperty.Register("FilePath", typeof(string), typeof(Thumbnail));

        public BitmapSource Image
        {
            get
            {
                if (_image== null)
                    _image = _imageGetter(this);
                return _image;
            }
            set { _image = value; }
        }
    }

    public class ImageLoaderProxyPixelData
    {
        public byte[] Pixels { get; set; }
        public Int32Rect Rect { get; set; }
        public int Stride { get; set; }
    }
}

Comme une référence, (pour moi-même si personne d'autre, je suppose) j'ai testé cette méthode sur un de 10 ans d'ordinateur portable avec un processeur Centrino et l'avait presque pas de problème de fluidité dans l'INTERFACE utilisateur.

1voto

Je souhaite que je pourrais prendre le crédit pour cela, mais je crois que la meilleure réponse est déjà là: Comment puis-je empêcher la collecte des ordures d'être appelé lors de l'appel de ShowDialog sur une xaml fenêtre?

Même à partir du code de la ProcessAdd méthode, on peut voir que rien n'est exécuté que si _totalMemory est assez petit. Donc, je pense que ce code est beaucoup plus facile à utiliser et avec moins d'effets secondaires:

typeof(BitmapImage).Assembly
  .GetType("MS.Internal.MemoryPressure")
  .GetField("_totalMemory", BindingFlags.NonPublic | BindingFlags.Static)
  .SetValue(null, Int64.MinValue / 2); 

Nous avons besoin de comprendre, cependant, que la méthode est censée faire, et le commentaire de l' .NET source est assez clair:

/// Avalon currently only tracks unmanaged memory pressure related to Images.  
/// The implementation of this class exploits this by using a timer-based
/// tracking scheme. It assumes that the unmanaged memory it is tracking
/// is allocated in batches, held onto for a long time, and released all at once
/// We have profiled a variety of scenarios and found images do work this way

Donc, ma conclusion est que par la désactivation de leur code, vous risquez de remplir votre mémoire à cause de la façon dont les images sont gérées. Cependant, puisque vous savez que l'application que vous utilisez est important et qu'il pourrait avoir un GC.Recueillir des appelés, l'un très simple et sûre correctif serait pour vous l'appelez-vous, lorsque vous vous sentez.

Le code il tente d'exécuter à chaque fois que la quantité totale de mémoire utilisée au-dessus d'un seuil, avec une minuterie de sorte qu'il n'arrive pas trop souvent. Qui serait de 30 secondes pour eux. Alors pourquoi ne pas vous appeler GC.Collecter(2) lorsque vous êtes à la fermeture de formes ou de faire d'autres choses qui libèrent l'utilisation de plusieurs images? Ou lorsque l'ordinateur est en veille ou l'application n'est pas une priorité, etc?

J'ai pris le temps de vérifier où le _totalMemory de la valeur et il semble que chaque fois qu'ils créent un WritableBitmap, ils ajouter de la mémoire pour qu'il _totalMemory, qui est calculé ici: http://referencesource.microsoft.com/PresentationCore/R/dca5f18570fed771.html en tant que pixelWidth * pixelHeight * pixelFormat.InternalBitsPerPixel / 8 * 2; et plus loin dans les méthodes qui fonctionnent avec Freezables. C'est un mécanisme interne pour garder une trace de la mémoire allouée par la représentation graphique de presque n'importe quel contrôle WPF.

Il me semble que vous pourriez non seulement _totalMemory à une valeur très faible, mais aussi détourner le mécanisme. Vous pouvait de temps en temps la lecture de la valeur, ajoutez-y la grande valeur que vous soustrait d'abord, et d'obtenir la valeur réelle de la mémoire utilisée par tirés des contrôles et de décider si vous voulez GC.Collecter ou pas.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X