Quand utiliser des références faibles dans .Net?

56

Personnellement, je n'ai pas rencontré de situation où j'ai eu besoin d'utiliser le type WeakReference dans .Net, mais la croyance populaire semble être qu'il devrait être utilisé dans les caches. Le Dr Jon Harrop a très bien argumenté contre l'utilisation de WeakReferences dans des caches dans sa réponse à cette question.

J'ai aussi souvent entendu des développeurs AS3 parler d'utiliser des références faibles pour économiser de la mémoire, mais d'après les conversations que j'ai eues, cela semble ajouter à la complexité sans nécessairement atteindre l'objectif visé, et le comportement à l'exécution est plutôt imprévisible. À tel point que beaucoup l'abandonnent simplement et gèrent l'utilisation de la mémoire avec plus de soin / optimisent leur code pour qu'il consomme moins de mémoire (ou fasse le compromis entre plus de cycles de calcul et une empreinte mémoire réduite).

Le Dr Jon Harrop a également souligné dans sa réponse que les références faibles .Net ne sont pas légères, et il existe une collection agressive de références faibles à gen0. Selon MSDN , les références longues et faibles vous permettent de recréer un objet but the state of the object remains unpredictable.!

Compte tenu de ces caractéristiques, je ne peux pas penser à une situation où des références faibles seraient utiles. Peut-être que quelqu'un pourrait m'éclairer?

le Burningmonk
la source
3
Vous avez déjà décrit les utilisations potentielles. Bien sûr, il existe d'autres moyens d'approcher ces situations, mais il existe plus d'un moyen de dépouiller un chat. Si vous recherchez un pare-balles, "vous devriez toujours utiliser un WeakReference lorsque X", je doute que vous en trouviez un.
2
@ itsme86 - Je ne cherche pas un cas d'utilisation à l'épreuve des balles, mais juste des cas pour lesquels des références faibles conviennent et ont du sens. Le cas d'utilisation du cache, par exemple, étant donné que les références faibles sont collectées avec tant d'empressement, cela entraînera tout simplement davantage d'
4
Je suis un peu déçu de voir qu'il y a beaucoup de votes. Cela ne me dérangerait pas de voir une réponse ou une discussion à ce sujet (en b4 "Stack Overflow n'est pas un forum").
ta.speot.is
@theburningmonk C'est le décalage en retour du gain de mémoire. Dans le cadre actuel, il est douteux que quiconque atteigne directement l'outil WeakReference même lors de la mise en œuvre d'un cache, car des systèmes de cache complets sont facilement disponibles.
Voici un exemple embarrassant et trop compliqué de leur utilisation (pour le modèle d'événements faibles décrit par ta.sotot.is ci-dessous)
Benjol le

Réponses:

39

J'ai trouvé des applications pratiques légitimes de références faibles dans les trois scénarios du monde réel suivants, qui m'est arrivé personnellement:

Application 1: gestionnaires d'événements

Vous êtes un entrepreneur Votre société vend un contrôle Spark Line pour WPF. Les ventes sont excellentes mais les coûts de support vous tuent. Trop de clients se plaignent de problèmes de processeur et de fuites de mémoire lorsqu'ils parcourent des écrans remplis de lignes d'étincelles. Le problème est que leur application crée de nouvelles lignes d'étincelle à mesure qu'elles apparaissent, mais la liaison de données empêche les anciennes d'être récupérées. Que faire?

Introduisez une référence faible entre la liaison de données et votre contrôle afin que la liaison de données à elle seule n'empêche plus votre contrôle d'être nettoyé. Ajoutez ensuite un finaliseur à votre contrôle qui arrête la liaison de données lorsqu’elle est collectée.

Application 2: graphes mutables

Vous êtes le prochain John Carmack. Vous avez inventé une nouvelle représentation graphique de surfaces de subdivisions hiérarchiques basée sur des graphes, qui donne aux jeux de Tim Sweeney des allures de Nintendo Wii. Évidemment, je ne vais pas vous dire exactement comment cela fonctionne, mais tout est centré sur ce graphe modifiable où les voisins d’un sommet peuvent être trouvés dans un fichier Dictionary<Vertex, SortedSet<Vertex>>. La topologie du graphique ne cesse de changer au fur et à mesure que le joueur tourne. Il n’ya qu’un problème: votre structure de données est en train de perdre des sous-graphes inaccessibles pendant son exécution et vous devez les supprimer sinon vous perdrez de la mémoire. Heureusement, vous êtes un génie. Vous savez donc qu'il existe une classe d'algorithmes spécialement conçus pour localiser et collecter des sous-graphiques inaccessibles: les éboueurs! Vous lisez l'excellente monographie de Richard Jones sur le sujetmais cela vous laisse perplexe et préoccupé par votre échéance imminente. Que faire?

En remplaçant simplement votre Dictionarytable par une table de hachage faible, vous pouvez superposer le GC existant et le faire collecter automatiquement les sous-graphiques inaccessibles pour vous! Retour à feuilleter des publicités Ferrari.

Application 3: Décorer des arbres

Vous êtes suspendu au plafond d'une pièce cyclindrique à un clavier. Vous avez 60 secondes pour passer au crible certaines données BIG avant que quelqu'un ne vous trouve. Vous vous êtes préparé avec un bel analyseur basé sur un flux qui s'appuie sur le GC pour collecter des fragments d'AST après leur analyse. Mais vous réalisez que vous avez besoin de métadonnées supplémentaires sur chaque AST Nodeet que vous en avez besoin rapidement. Que faire?

Vous pouvez utiliser a Dictionary<Node, Metadata>pour associer des métadonnées à chaque nœud, mais, à moins que vous ne l'effaciez, les références fortes du dictionnaire aux anciens nœuds AST les maintiendront en vie et entraîneront une fuite de mémoire. La solution est une table de hachage faible qui ne conserve que les références faibles aux clés et garbage collecte les liaisons clé-valeur lorsque la clé devient inaccessible. Ensuite, lorsque les noeuds AST deviennent inaccessibles, ils sont collectés et leur liaison clé-valeur est supprimée du dictionnaire, ce qui rend les métadonnées correspondantes inaccessibles. Ensuite, tout ce que vous avez à faire après la fin de votre boucle principale est de glisser vers le haut dans l’aération en vous rappelant de la remplacer au moment même où le gardien de sécurité arrive.

Notez que dans les trois applications du monde réel qui m’étaient arrivées, je voulais que le GC collecte le plus agressivement possible. C'est pourquoi ce sont des applications légitimes. Tout le monde a tort.

Jon Harrop
la source
2
Les références faibles ne fonctionneront pas pour l'application 2 si les sous-graphes inaccessibles contiennent des cycles. En effet, une table de hachage faible comporte généralement des références faibles aux clés, mais des références fortes aux valeurs. Vous auriez besoin d'une table de hachage qui conserve des références fortes aux valeurs uniquement lorsque la clé est toujours accessible -> voir les éphémérons ( ConditionalWeakTableen .NET).
Daniel
@ Daniel Le GC n'est-il pas censé être capable de gérer des cycles inaccessibles? Comment cela ne serait-il pas collecté lorsqu'un cycle inaccessible de références fortes serait collecté?
binki
Oh, je pense que je vois. Je viens de supposer que ConditionalWeakTablec’est ce que les applications 2 et 3 utiliseraient, alors que certaines personnes d’autres postes l’utilisent Dictionary<WeakReference, T>. Vous ne savez pas pourquoi, vous vous retrouverez toujours avec une tonne de WeakReferencevaleurs nuls avec des valeurs auxquelles aucune clé ne peut accéder, quelle que soit la méthode utilisée . Ridik.
binki
@binki: "Le GC n'est-il pas censé être capable de gérer des cycles inaccessibles? Comment cela ne serait-il pas collecté lorsqu'un cycle inaccessible de références fortes serait collecté?". Vous avez un dictionnaire indexé sur des objets uniques qui ne peuvent pas être recréés. Lorsque l'un de vos objets clés devient inaccessible, il peut être récupéré, mais la valeur correspondante dans le dictionnaire ne sera même pas supposée être théoriquement inaccessible car un dictionnaire ordinaire conservera une référence forte le conservant en vie. Donc, vous utilisez un dictionnaire faible.
Jon Harrop
@ Daniel: "Les références faibles ne fonctionneront pas pour l'application 2 si les sous-graphes inaccessibles contiennent des cycles. En effet, une table de hachage faible comporte généralement des références faibles aux clés, mais des références fortes aux valeurs. Vous auriez besoin d'une table de hachage qui conserve des références fortes aux valeurs uniquement tant que la clé est toujours accessible ". Oui. Il est probablement préférable de coder directement le graphique avec des arêtes comme pointeurs afin que le CPG le collecte lui-même.
Jon Harrop
19

Compte tenu de ces caractéristiques, je ne peux pas penser à une situation où des références faibles seraient utiles. Peut-être que quelqu'un pourrait m'éclairer?

Microsoft document Modèles d'événements faibles .

Dans les applications, il est possible que les gestionnaires attachés aux sources d'événements ne soient pas détruits en coordination avec l'objet écouteur ayant associé le gestionnaire à la source. Cette situation peut entraîner des fuites de mémoire. Windows Presentation Foundation (WPF) introduit un modèle de conception qui peut être utilisé pour résoudre ce problème, en fournissant une classe de gestionnaire dédiée pour des événements particuliers et en implémentant une interface sur des écouteurs pour cet événement. Ce modèle de conception est appelé modèle d'événements faibles.

...

Le modèle d'événement faible est conçu pour résoudre ce problème de fuite de mémoire. Le modèle d'événement faible peut être utilisé chaque fois qu'un écouteur doit s'enregistrer pour un événement, mais ce dernier ne sait pas explicitement quand annuler l'enregistrement. Le modèle d'événement faible peut également être utilisé chaque fois que la durée de vie de l'objet source de la source dépasse la durée de vie utile de l'écouteur. (Dans ce cas, vous déterminez l'utile.) Le modèle d'événement faible permet à l'auditeur de s'inscrire et de recevoir l'événement sans affecter les caractéristiques de durée de vie de l'objet de l'écouteur. En effet, la référence implicite de la source ne détermine pas si l'auditeur est éligible pour la récupération de place. La référence est une référence faible, d'où la dénomination du modèle d'événement faible et des API associées.. L’auditeur peut être collecté ou détruit d’une autre manière et la source peut continuer sans conserver les références de gestionnaire non collectables à un objet maintenant détruit.

ta.speot.is
la source
Cette URL sélectionne automatiquement la version la plus récente de .NET (4.5 actuellement) dans laquelle "ce sujet n'est plus disponible". La sélection de .NET 4.0 au lieu de cela fonctionne ( msdn.microsoft.com/en-us/library/aa970850(v=vs.100).aspx )
maxp
13

Permettez-moi de commencer par ceci et d'y revenir:

Un WeakReference est utile lorsque vous souhaitez garder un onglet sur un objet, mais vous NE voulez PAS que vos observations empêchent la collecte de cet objet.

Commençons donc depuis le début:

... convient à l'avance pour toute infraction non intentionnelle, mais je vais revenir au niveau "Dick et Jane" pour un moment, car on ne peut jamais dire à son public.

Ainsi, lorsque vous avez un objet X- spécifions-le comme une instance de class Foo- il NE PEUT PAS vivre seul (généralement vrai); De la même manière que "Aucun homme n'est une île", un objet ne peut être promu en îlot que de quelques manières - bien que cela s'appelle être une racine GC dans le langage CLR. Le fait d'être une racine du GC ou d'avoir une chaîne établie de connexions / références à une racine du GC est essentiellement ce qui détermine si les Foo x = new Foo()déchets sont collectés ou non .

Si vous ne pouvez pas retrouver votre chemin vers une racine GC, soit en marchant en tas, soit en pile, vous êtes effectivement orphelin et serez probablement marqué / collecté au prochain cycle.

À ce stade, examinons quelques exemples horribles:

Tout d'abord, notre Foo:

public class Foo 
{
    private static volatile int _ref = 0;
    public event EventHandler FooEvent;
    public Foo()
    {
        _ref++;
        Console.WriteLine("I am #{0}", _ref);
    }
    ~Foo()
    {
        Console.WriteLine("#{0} dying!", _ref--);
    }
}

Assez simple - il n'est pas thread-safe, donc n'essayez pas cela, mais conserve un "décompte de références" approximatif d'instances actives et diminue lorsqu'elles sont finalisées.

Maintenant, regardons un FooConsumer:

public class NastySingleton
{
    // Static member status is one way to "get promoted" to a GC root...
    private static NastySingleton _instance = new NastySingleton();
    public static NastySingleton Instance { get { return _instance;} }

    // testing out "Hard references"
    private Dictionary<Foo, int> _counter = new Dictionary<Foo,int>();
    // testing out "Weak references"
    private Dictionary<WeakReference, int> _weakCounter = new Dictionary<WeakReference,int>();

    // Creates a strong link to Foo instance
    public void ListenToThisFoo(Foo foo)
    {
        _counter[foo] = 0;
        foo.FooEvent += (o, e) => _counter[foo]++;
    }

    // Creates a weak link to Foo instance
    public void ListenToThisFooWeakly(Foo foo)
    {
        WeakReference fooRef = new WeakReference(foo);
        _weakCounter[fooRef] = 0;
        foo.FooEvent += (o, e) => _weakCounter[fooRef]++;
    }

    private void HandleEvent(object sender, EventArgs args, Foo originalfoo)
    {
        Console.WriteLine("Derp");
    }
}

Nous avons donc un objet qui est déjà une racine GC propre (enfin ... pour être précis, il sera enraciné via une chaîne directement dans le domaine d'applications exécutant cette application, mais c'est un autre sujet) qui a deux méthodes. de verrouiller sur une Fooinstance - testons-le:

// Our foo
var f = new Foo();

// Create a "hard reference"
NastySingleton.Instance.ListenToThisFoo(f);

// Ok, we're done with this foo
f = null;

// Force collection of all orphaned objects
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

Maintenant, à partir de ce qui précède, vous attendez-vous à ce que l'objet-qui-était-une-fois-mentionné-par- fsoit soit "collectionable"?

Non, car il existe maintenant un autre objet contenant une référence, le Dictionarydans cette Singletoninstance statique.

Ok, essayons l'approche faible:

f = new Foo();
NastySingleton.Instance.ListenToThisFooWeakly(f);

// Ok, we're done with this foo
f = null;

// Force collection of all orphaned objects
// This should collect # 2 - you'll see a "#2 dying"
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

Maintenant, lorsque nous détruisons notre référence à Foo-ce qui était autrefois f, il n'y a plus de références "dures" à l'objet, il est donc collectable - la WeakReferencecréation par le faible auditeur ne l'empêchera pas.

Bons cas d'utilisation:

  • Gestionnaires d'événements (bien que lisez ceci en premier: événements faibles en C # )

  • Vous avez une situation dans laquelle vous provoqueriez une "référence récursive" (l'objet A fait référence à l'objet B, qui fait référence à l'objet A, également appelé "fuite de mémoire") (edit: derp, bien sûr, il s'agit pas vrai)

  • Vous voulez "diffuser" quelque chose à une collection d'objets, mais vous ne voulez pas être la chose qui les garde en vie; un List<WeakReference>peut être maintenu facilement, et même élagué en enlevant oùref.Target == null

JerKimball
la source
1
En ce qui concerne votre deuxième cas d'utilisation, le ramasse-miettes gère très bien les références circulaires. "objet A se réfère à l'objet B, qui se réfère à l'objet A" n'est certainement pas une fuite de mémoire.
Joe Daley
@ JoeDaley je suis d'accord. Le .NET GC utilise un algorithme de marquage et de balayage qui (je crois le rappeler correctement) marque tous les objets à collectionner, puis suit les références à partir de "racines" (références d'objets sur la pile, objets statiques), annulant la sélection des objets à collectionner. . Si une référence circulaire existe mais qu'aucun des objets n'est accessible à partir d'une racine, les objets ne sont pas désélectionnés pour la collecte et sont donc éligibles pour la collecte.
ta.speot.is
1
@JoeDaley - Vous avez tous les deux raison, c'est exact - vous vous précipitiez là vers la fin ... Je vais l'éditer.
JerKimball
4

entrez la description de l'image ici

Comme des fuites logiques qui sont très difficiles à détecter alors que les utilisateurs ont tendance à remarquer que faire fonctionner votre logiciel pendant longtemps a tendance à prendre de plus en plus de mémoire et à devenir de plus en plus lent jusqu'à ce qu'ils redémarrent? Je ne.

Considérez ce qui se passe si, lorsque l'utilisateur demande à supprimer la ressource d'application ci-dessus, Thing2ne parvient pas à gérer correctement un tel événement sous:

  1. Pointeurs
  2. Références fortes
  3. Références faibles

... et en vertu de laquelle l'une de ces erreurs serait vraisemblablement détectée lors des tests, et laquelle ne volerait pas sous le radar comme un insecte de chasse furtif. La propriété partagée est, plus souvent que la plupart, une idée absurde.


la source
1

Le ConditionalWeakTable est un exemple très illustratif de références faibles utilisées à bon escient . Il est utilisé par le DLR (entre autres endroits) pour attacher des "membres" supplémentaires à des objets.

Vous ne voulez pas que la table garde l'objet en vie. Ce concept ne pourrait tout simplement pas fonctionner sans de faibles références.

Mais il me semble que toutes les utilisations des références faibles sont venues longtemps après leur ajout au langage, car les références faibles font partie de .NET depuis la version 1.1. Il semble que vous souhaitiez simplement ajouter quelque chose, de sorte que le manque de destruction déterministe ne vous empêche pas de tourner la page du point de vue des fonctionnalités linguistiques.

GregRos
la source
J'ai en fait découvert que, bien que la table utilise le concept de références faibles, l'implémentation réelle n'implique pas le WeakReferencetype, car la situation est beaucoup plus complexe. Il utilise différentes fonctionnalités exposées par le CLR.
GregRos
-2

Si la couche de cache est implémentée avec C #, il est préférable de placer vos données dans le cache sous forme de références faibles, cela pourrait aider à améliorer les performances de votre couche de cache.

Pensez que cette approche pourrait également être appliquée à la mise en œuvre de session. Comme la session est un objet vivant longtemps la plupart du temps, il peut arriver que vous n'ayez pas de mémoire pour le nouvel utilisateur. Dans ce cas, il sera beaucoup mieux de supprimer un autre objet de session utilisateur que de lancer OutOfMemoryException.

De plus, si vous avez un objet volumineux dans votre application (une grande table de recherche, etc.), vous devriez l'utiliser très rarement et la recréation d'un tel objet n'est pas une procédure très coûteuse. Mieux vaut alors avoir comme moyen de référence la semaine de pouvoir libérer votre mémoire lorsque vous en avez vraiment besoin.

Ph0en1x
la source
5
mais le problème avec les références faibles (voir la réponse que j'ai citée) est qu'elles sont très rapidement collectées et que la collection n'est pas liée à la disponibilité d'espace mémoire. Donc, vous vous retrouvez avec plus de cache en absence lorsqu'il n'y a pas de pression sur la mémoire.
1
Pour ce qui est du deuxième point, à propos des objets volumineux, le document MSDN indique que, même si de longues références faibles vous permettent de recréer un objet, son état reste imprévisible. Si vous voulez le recréer à partir de zéro à chaque fois, pourquoi ne pas utiliser une référence faible alors que vous pouvez simplement appeler une fonction / méthode pour le créer à la demande et renvoyer une instance transitoire?
Il y a un cas où la mise en cache est utile: si on crée fréquemment des objets immuables, beaucoup d'entre eux seront identiques (par exemple, en lisant plusieurs lignes d'un fichier censé avoir plusieurs doublons), chaque chaîne sera créée en tant que nouvel objet. , mais si une ligne correspond à une autre ligne à laquelle une référence existe déjà, l'efficacité de la mémoire peut être améliorée si la nouvelle instance est abandonnée et qu'une référence à l'instance préexistante est remplacée. Notez que cette substitution est utile car l’autre référence est de toute façon en attente. Si ce n'était pas du code, gardez le nouveau.
Supercat