web-dev-qa-db-fra.com

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

Je n'ai pas personnellement rencontré de situation où j'ai 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 donné un très bon argument contre l'utilisation de WeakReferences dans les caches dans sa réponse à cette question.

J'ai également souvent entendu des développeurs AS3 parler de l'utilisation de références faibles pour économiser sur l'empreinte mémoire, mais d'après les conversations que j'ai eues, il semble ajouter de la complexité sans nécessairement atteindre l'objectif visé, et le comportement d'exécution est plutôt imprévisible. À tel point que beaucoup y renoncent tout simplement et gèrent plus soigneusement l'utilisation de la mémoire/optimisent leur code pour consommer moins de mémoire (ou faire le compromis entre plus de cycles CPU et une plus petite empreinte mémoire).

Le Dr Jon Harrop a également souligné dans sa réponse que les références faibles .Net ne sont pas molles et qu'il existe une collection agressive de références faibles à gen0. Selon MSDN , de longues références 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 imaginer une situation où des références faibles seraient utiles, peut-être que quelqu'un pourrait m'éclairer?

57
theburningmonk

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

Application 1: gestionnaires d'événements

Vous êtes entrepreneur. Votre entreprise vend un contrôle spark lines pour WPF. Les ventes sont excellentes mais les coûts de support vous tuent. Trop de clients se plaignent de la surcharge du processeur et des fuites de mémoire lorsqu'ils parcourent des écrans pleins de lignes spark. Le problème est que leur application crée de nouvelles lignes spark comme ils apparaissent, mais la liaison de données empêche les anciens d'être récupérés. Que faites-vous?

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

Application 2: Graphes mutables

Tu es le prochain John Carmack. Vous avez inventé une nouvelle représentation graphique ingénieuse des surfaces de subdivision hiérarchique qui fait ressembler les jeux de Tim Sweeney à une Nintendo Wii. Évidemment, je ne vais pas vous dire exactement comment cela fonctionne mais tout est centré sur ce graphe mutable où les voisins d'un sommet peuvent être trouvés dans un Dictionary<Vertex, SortedSet<Vertex>>. La topologie du graphique continue de changer au fur et à mesure que le joueur court. Il n'y a qu'un seul problème: votre structure de données élimine les sous-graphiques inaccessibles pendant son exécution et vous devez les supprimer ou 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 les sous-graphiques inaccessibles: les ramasseurs d'ordures! Vous lisez excellente monographie de Richard Jones sur le sujet mais cela vous laisse perplexe et préoccupé par votre échéance imminente. Que faire?

En remplaçant simplement votre Dictionary par une table de hachage faible, vous pouvez superposer le GC existant et le faire collecter automatiquement vos sous-graphiques inaccessibles pour vous! Retour à feuilleter les 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 quelques BIG DATA avant que quelqu'un ne vous trouve. Vous êtes venu préparé avec un magnifique analyseur basé sur le flux qui s'appuie sur le GC pour collecter des fragments de AST après avoir été analysés. Mais vous vous rendez compte que vous avez besoin de métadonnées supplémentaires sur chaque AST Node et vous en avez besoin rapidement. Que faites-vous?

Vous pouvez utiliser un Dictionary<Node, Metadata> pour associer des métadonnées à chaque nœud mais, à moins que vous ne les effaciez, les références fortes du dictionnaire aux anciens nœuds AST les garderont en vie et feront fuir la mémoire. La solution est un table de hachage faible qui ne conserve que les références faibles aux clés et les ordures récupère les liaisons de valeur-clé lorsque la clé devient inaccessible. Ensuite, comme AST = les nœuds deviennent inaccessibles, ils sont récupérés et leur liaison de valeur-clé est supprimée du dictionnaire, ce qui laisse les métadonnées correspondantes inaccessibles afin qu'elles soient également collectées. en pensant à le remplacer juste au moment où le gardien de sécurité entre.

Notez que dans ces trois applications du monde réel qui m'est réellement arrivé, je souhaitais que le GC recueille le plus agressivement possible. C'est pourquoi ce sont des applications légitimes. Tout le monde a tort.

40
Jon Harrop

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

Document Microsoft 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 qui a attaché 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 les écouteurs pour cet événement. Ce modèle de conception est appelé modèle d'événement faible.

...

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'inscrire à un événement, mais l'écouteur ne sait pas explicitement quand se désinscrire. Le modèle d'événement faible peut également être utilisé chaque fois que la durée de vie de l'objet de la source dépasse la durée de vie utile de l'objet de l'écouteur. (Dans ce cas, vous déterminez l'utilité.) 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'écouteur est éligible pour le garbage collection. 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'écouteur peut être récupéré 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.

19
ta.speot.is

Permettez-moi de mettre cela en premier et d'y revenir:

Une référence faible est utile lorsque vous souhaitez garder un œil sur un objet, mais vous ne voulez PAS que vos observations empêchent cet objet d'être collecté

Commençons donc par le début:

- excuses à l'avance pour toute infraction involontaire, mais je vais revenir au niveau "Dick et Jane" pendant un moment car on ne peut jamais le dire à son public.

Donc, quand vous avez un objet X - spécifions-le comme une instance de class Foo - il NE PEUT PAS vivre de lui-même (surtout vrai); De la même manière que "Aucun homme n'est une île", il n'y a que quelques façons qu'un objet peut être promu à Islandhood - bien que cela s'appelle être une racine GC dans CLR. Être une racine GC, ou avoir une chaîne établie de connexions/références à une racine GC, est fondamentalement ce qui détermine si Foo x = new Foo() récupère les ordures.

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

À ce stade, regardons quelques exemples horriblement artificiels:

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 - ce n'est pas sûr pour les threads, alors n'essayez pas, mais conserve un "nombre de références" approximatif des instances actives et des diminutions lorsqu'elles sont finalisées.

Voyons maintenant 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 vers le domaine d'application exécutant cette application, mais c'est un autre sujet) qui a deux méthodes de verrouillage sur une instance de Foo - 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, d'après ce qui précède, vous attendriez-vous à ce que l'objet-qui-était-une-fois-référencé par f soit "collectable"?

Non, car un autre objet contient maintenant une référence - Dictionary dans cette instance statique Singleton.

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- qui-était-une fois -f, il n'y a plus de références "dures" à l'objet, donc il est collectable - le WeakReference créé par l'écouteur faible n'empêchera pas cela.

Bons cas d'utilisation:

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

  • Vous avez une situation où vous provoqueriez une "référence récursive" (c'est-à-dire que 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 ce n'est 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 supprimant où ref.Target == null

13
JerKimball

enter image description here

Comme des fuites logiques qui sont vraiment difficiles à localiser alors que les utilisateurs ont tendance à remarquer que l'exécution de votre logiciel pendant une longue période a tendance à prendre de plus en plus de mémoire et à devenir de plus en plus lente jusqu'au redémarrage? Je ne.

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

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

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

4
user204677

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

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

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. Cela semble être quelque chose que vous voudriez ajouter, de sorte que le manque de destruction déterministe ne vous fasse pas reculer en ce qui concerne les fonctionnalités du langage.

1
GregRos