¿Cuándo usar referencias débiles en .Net?

50

Personalmente, no he encontrado una situación en la que haya necesitado usar el tipo WeakReference en .Net, pero la creencia popular parece ser que debería usarse en cachés. El Dr. Jon Harrop dio un muy buen caso contra el uso de WeakReferences en cachés en su respuesta a esta pregunta.

También he escuchado a menudo a los desarrolladores de AS3 hablar sobre el uso de referencias débiles para ahorrar espacio en la memoria, pero según las conversaciones que he tenido, parece que agrega complejidad sin necesariamente cumplir con el objetivo previsto, y el comportamiento en tiempo de ejecución es bastante impredecible. Tanto es así que muchos simplemente lo abandonan y, en su lugar, administran el uso de la memoria con más cuidado / optimizan su código para que requieran menos memoria (o para compensar más ciclos de CPU y una menor huella de memoria).

El Dr. Jon Harrop también señaló en su respuesta que las referencias débiles de .Net no son blandas, y hay una colección agresiva de referencias débiles en gen0. De acuerdo con MSDN , las referencias largas y débiles le ofrecen la posibilidad de recrear un objeto, but the state of the object remains unpredictable. !

Dadas estas características, no puedo pensar en una situación en la que las referencias débiles sean útiles, ¿tal vez alguien me pueda iluminar?

    
pregunta theburningmonk 31.01.2013 - 00:40

6 respuestas

34

He encontrado aplicaciones prácticas legítimas de referencias débiles en los siguientes tres escenarios del mundo real que realmente me sucedieron personalmente:

Aplicación 1: Controladores de eventos

Eres un empresario. Su empresa vende un control de

href="http://www.edwardtufte.com/bboard/q-and-a-fetch-msg?msg_id=0001OR"> sparklines para WPF. Las ventas son excelentes, pero los costos de soporte lo están matando. Demasiados clientes se quejan de acaparamiento de CPU y pérdidas de memoria cuando se desplazan por pantallas llenas de líneas de chispas. El problema es que su aplicación está creando nuevas líneas de chispa a medida que se ven, pero el enlace de datos impide que se recolecten los antiguos. ¿Qué haces?

Introduzca una referencia débil entre el enlace de datos y su control, de modo que el enlace de datos solo ya no evitará que se recolecte su control. Luego, agregue un finalizador a su control que destruya el enlace de datos cuando se recopile.

Aplicación 2: Gráficos mutables

Tú eres el próximo John Carmack. Has inventado una nueva e ingeniosa representación gráfica de superficies de subdivisión jerárquica que hace que los juegos de Tim Sweeney parezcan una Wii de Nintendo. Obviamente, no te voy a decir exactamente cómo funciona pero todo se centra en este gráfico mutable donde los vecinos de un vértice se puede encontrar en un Dictionary<Vertex, SortedSet<Vertex>> . La topología del gráfico sigue cambiando a medida que el jugador corre. Solo hay un problema: su estructura de datos está generando subgrafos inalcanzables a medida que se ejecutan y usted necesita eliminarlos o perderá memoria. Por suerte, eres un genio, así que sabes que hay una clase de algoritmos diseñados específicamente para localizar y recolectar subgrafos inalcanzables: ¡recolectores de basura! Lees la excelente monografía de Richard Jones sobre el tema , pero te deja perplejo y preocupado por tu fecha límite inminente. ¿Qué haces?

¡Simplemente al reemplazar su Dictionary con una tabla hash débil, puede usar el GC existente y hacer que recoja automáticamente sus subgrafos inalcanzables! Volver a hojear anuncios de Ferrari.

Aplicación 3: decorar árboles

Estás colgando del techo de una habitación cíclica en un teclado. Tienes 60 segundos para analizar algunos DATOS GRANDES antes de que alguien te encuentre. Viniste preparado con un hermoso analizador basado en flujo que se basa en el GC para recopilar fragmentos de AST después de que hayan sido analizados. Pero se da cuenta de que necesita metadatos adicionales en cada AST Node y lo necesita rápidamente. ¿Qué haces?

Puede usar un Dictionary<Node, Metadata> para asociar los metadatos con cada nodo, pero, a menos que lo borre, las referencias sólidas del diccionario a los nodos AST antiguos los mantendrán con vida y perderán memoria. La solución es una tabla hash débil que mantiene solo las referencias débiles a las claves y la basura recolecta los enlaces de valor-clave cuando la clave se vuelve inaccesible. Luego, a medida que los nodos de AST se vuelven inalcanzables, se recolectan los residuos y su enlace de valor-clave se elimina del diccionario, dejando los metadatos correspondientes inalcanzables, por lo que también se recopilan. Luego, todo lo que tiene que hacer después de que termine su bucle principal es deslizar hacia arriba a través de la ventilación de aire, recordando reemplazarlo justo cuando entra el guardia de seguridad.

Tenga en cuenta que en las tres aplicaciones del mundo real que realmente me sucedieron, quería que el GC recolectara de la manera más agresiva posible. Es por eso que estas son aplicaciones legítimas. Todos los demás están equivocados.

    
respondido por el Jon Harrop 08.02.2013 - 02:10
18
  

Dadas estas características, no puedo pensar en una situación en la que las referencias débiles sean útiles, ¿tal vez alguien me pueda iluminar?

Documento de Microsoft Patrones de eventos débiles .

  

En las aplicaciones, es posible que los controladores que se adjuntan a las fuentes de eventos no se destruyan en coordinación con el objeto de escucha que asoció el controlador a la fuente. Esta situación puede llevar a fugas de memoria. Windows Presentation Foundation (WPF) introduce un patrón de diseño que puede usarse para solucionar este problema, al proporcionar una clase de administrador dedicado para eventos particulares e implementar una interfaz en los oyentes para ese evento. Este patrón de diseño se conoce como patrón de evento débil.

     

...

     

El patrón de evento débil está diseñado para resolver este problema de pérdida de memoria. El patrón de evento débil puede usarse siempre que un oyente necesite registrarse para un evento, pero el oyente no sabe explícitamente cuándo debe darse de baja. El patrón de evento débil también se puede usar cuando la vida útil del objeto de la fuente excede la vida útil del objeto útil del oyente. (En este caso, la utilidad la determina usted). El patrón de evento débil le permite al oyente registrarse y recibir el evento sin afectar las características de vida útil del objeto del oyente de ninguna manera. En efecto, la referencia implícita de la fuente no determina si el oyente es elegible para la recolección de basura. La referencia es una referencia débil, por lo tanto, la denominación del patrón de evento débil y las API relacionadas . El oyente puede ser recolectado o destruido de otra manera, y la fuente puede continuar sin retener referencias de controladores no recopilables a un objeto ahora destruido.

    
respondido por el ta.speot.is 31.01.2013 - 01:06
11

Déjame poner esto primero y volver a ello:

  

Una WeakReference es útil cuando desea mantener las pestañas de un objeto, pero NO desea que sus observaciones impidan que se recoja ese objeto

Así que vamos a empezar desde el principio:

--apologías por adelantado para cualquier ofensa no intencional, pero voy a retroceder al nivel de "Dick y Jane" por un momento ya que uno nunca puede decirle a la audiencia.

Entonces, cuando tienes un objeto X , especifiquémoslo como una instancia de class Foo , NO PUEDE vivir por sí solo (la mayoría es verdadera); De la misma manera que "Ningún hombre es una isla", solo hay algunas formas en que un objeto puede ser promovido a la Isla, aunque a esto se le llama ser una raíz GC en el lenguaje CLR. Ser una raíz de GC, o tener una cadena establecida de conexiones / referencias a una raíz de GC, es básicamente lo que determina si Foo x = new Foo() obtiene o no recolección de basura.

Si no puede caminar de regreso a alguna raíz de GC, ya sea por montones o apilados, quedará efectivamente huérfano y probablemente será marcado / recolectado en el próximo ciclo.

En este punto, veamos algunos ejemplos mal construidos:

Primero, nuestro 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--);
    }
}

Bastante simple: no es seguro para subprocesos, así que no lo intentes, pero mantiene un "recuento de referencias" aproximado de instancias activas y disminuye cuando se finalizan.

Ahora veamos 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");
    }
}

Entonces, tenemos un objeto que ya es una raíz de GC propia (bueno ... para ser específico, estará enraizado a través del dominio de la aplicación que ejecuta esta aplicación, pero ese es otro tema) que tiene dos métodos de enganchar a una instancia Foo - probémosla:

// 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();

Ahora, a partir de lo anterior, ¿esperaría que el objeto-that-was-once-referido por f sea "coleccionable"?

No, porque ahora hay otro objeto que contiene una referencia a él: el Dictionary en esa instancia estática Singleton .

Ok, probemos el enfoque débil:

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

Ahora, cuando eliminamos nuestra referencia a- Foo -th-was-once- f , no hay más referencias "duras" al objeto, por lo que es coleccionable: el WeakReference creado por el oyente débil no evitará eso.

Buenos casos de uso:

  • Controladores de eventos (aunque lea esto primero: Eventos débiles en C # )

  • Tiene una situación en la que causaría una "referencia recursiva" (es decir, el objeto A se refiere al objeto B, que se refiere al objeto A, también conocido como "Pérdida de memoria") (edit: derp, por supuesto, esto no es cierto)

  • Quieres "transmitir" algo a una colección de objetos, pero no quieres ser lo que los mantiene vivos; un List<WeakReference> se puede mantener fácilmente, e incluso podarse eliminando donde ref.Target == null

respondido por el JerKimball 31.01.2013 - 01:40
3

¿Al igual que las fugas lógicas que son realmente difíciles de rastrear, mientras que los usuarios tienden a notar que la ejecución de su software por un largo tiempo tiende a requerir más y más memoria y se vuelve más y más lenta hasta que se reinicia? Yo no.

Considere lo que sucede si, cuando el usuario solicita eliminar el recurso de la aplicación anterior, Thing2 no puede manejar adecuadamente dicho evento en:

  1. punteros
  2. Referencias fuertes
  3. Referencias débiles

... y bajo el cual uno de estos errores podría ser atrapado durante las pruebas, y cuál no lo haría y volaría bajo el radar como un bicho de caza furtivo. La propiedad compartida es, más a menudo que la mayoría, una idea sin sentido.

    
respondido por el user204677 04.01.2016 - 19:25
1

Un ejemplo muy ilustrativo de referencias débiles usadas para un buen efecto es el ConditionalWeakTable , que es utilizado por el DLR (entre otros lugares) para adjuntar "miembros" adicionales a los objetos.

No quieres que la tabla mantenga el objeto vivo. Este concepto simplemente no podría funcionar sin referencias débiles.

Pero me parece que todos los usos de las referencias débiles vinieron mucho después de que se agregaron al lenguaje, ya que las referencias débiles han sido parte de .NET desde la versión 1.1. Parece algo que querrías agregar, por lo que la falta de destrucción determinista no te llevará a un rincón en lo que respecta a las características del idioma.

    
respondido por el GregRos 04.01.2016 - 20:13
-2

Si tiene una capa de caché implementada con C #, también es mucho mejor poner sus datos en caché como referencias débiles, podría ayudar a mejorar el rendimiento de su capa de caché.

Piense que este enfoque también podría aplicarse a la implementación de la sesión. Debido a que la sesión es un objeto de larga duración la mayor parte del tiempo, podría ser un caso en el que no tenga memoria para un nuevo usuario. En ese caso, será mucho mejor eliminar otro objeto de sesión de usuario y luego lanzar OutOfMemoryException.

Además, si tiene un objeto grande en su aplicación (alguna tabla de búsqueda grande, etc.), se debe utilizar con bastante frecuencia y la recreación de tal objeto no es un procedimiento muy costoso. Entonces mejor que sea como una semana de referencia para tener una manera de liberar tu memoria cuando realmente lo necesites.

    
respondido por el Ph0en1x 31.01.2013 - 01:01

Lea otras preguntas en las etiquetas

Comentarios Recientes

¶ Aunque .Net ya admite la verificación de referencias, puede verificar si un objeto pertenece a una clase simplemente leyendo una referencia concreta y luego verificando cada miembro primero: usando Runtime = Thread. Actual ; usando tiempo de ejecución; usando unit = System; usando Comparable ; public class Friend {public bool HasOwnProperty (objeto obj) {int i = obj. nombre ; Depurar. WriteLine (! (I! = Null)? "Tiene propiedad": "no propiedad"); yo ; } Nombre de cadena privada {get {return obj. GetName (); }... Lee mas