¿Deben los oyentes de eventos mantenerse en referencias débiles?

9

Por lo general, los oyentes de eventos no deberían sobrevivir a los objetos que los registraron.

¿Significa que los oyentes de eventos deberían estar sujetos a referencias débiles por defecto (almacenadas en colecciones débiles por las que los oyentes objeto están registrados)?

¿Hay casos válidos en los que el oyente debería sobrevivir a su creador?

¿O tal vez una situación como esa es un error y no debería permitirse?

mrpyo
fuente
Las referencias débiles generalmente están representadas por instancias, y estas instancias también pueden acumularse hasta el punto en que deben ser recolectadas como basura. Entonces no es un almuerzo gratis. La misma lógica que elimina las referencias débiles podría eliminar las referencias fuertes.
Frank Hileman

Respuestas:

7

¿Por qué los oyentes de eventos no deberían sobrevivir al objeto que los registró? Parece que está asumiendo que los oyentes de eventos deberían registrarse por métodos de controles (si tomamos el ejemplo de la GUI), o más precisamente, métodos por objetos de clases que heredan los controles del kit de herramientas de la GUI. Eso no es una necesidad: podría, por ejemplo, usar un objeto especializado para registrar oyentes de eventos y luego deshacerse de ese objeto.

Además, si los oyentes de eventos se derivaron débilmente, tendría que mantener referencias a ellos incluso si nunca usa esa referencia. De lo contrario, el oyente se recogerá en un momento aleatorio. Entonces, tenemos un error que es

  • Fácil de crear por error (todo lo que tiene que hacer es olvidarse de almacenar un objeto en una variable de referencia que nunca usará).
  • Difícil de notar (solo obtendrá ese error si el GC recolecta ese objeto).
  • Difícil de depurar (en la sesión de depuración, que siempre funciona como una sesión de lanzamiento, solo encontrará ese error si el GC recolectó el objeto).

Y si evitar ese error no es un incentivo suficiente, aquí hay algunos más:

  1. Tendrá que pensar en un nombre para cada oyente que cree.

  2. Algunos idiomas usan análisis estático que generarán una advertencia si tiene un campo de miembro privado que nunca se escribe o nunca se lee. Tendrá que usar un mecanismo para anular eso.

  3. El oyente de eventos hace algo, y una vez que el objeto que tiene su fuerte referencia se recopila, dejará de hacer eso. Ahora tiene algo que afecta el estado del programa y depende del GC, lo que significa que el GC afecta el estado concreto del programa. ¡Y esto es MALO !

  4. El manejo de referencias débiles es más lento, ya que tiene otro nivel de indirección y necesita verificar si se recopiló la referencia. Esto no sería un problema si fuera necesario tener oyentes de eventos con referencias débiles, ¡pero no lo es!

Idan Arye
fuente
5

En general, sí, deben usarse referencias débiles. Pero primero tenemos que tener claro lo que quiere decir con "oyentes de eventos".

Devoluciones de llamada

En algunos estilos de programación, especialmente en el contexto de operaciones asincrónicas, es común representar una parte de un cálculo como una devolución de llamada que se ejecuta en un evento determinado. Por ejemplo, un Promise[ 1 ] puede tener un thenmétodo que registre una devolución de llamada al completar el paso anterior:

promise =
    Promise.new(async_task)                # - kick off a task
    .then(value => operation_on(value))    # - queue other operations
    .then(value => other_operation(value)) #   that get executed on completion
... # do other stuff in the meanwhile
# later:
result = promise.value # block for the result

Aquí, las devoluciones de llamada registradas por thendeben ser retenidas por referencias fuertes, ya que la promesa (el origen del evento) es el único objeto que contiene una referencia a la devolución de llamada. Esto no es un problema, ya que la promesa en sí misma tiene una vida limitada, y se recolectará basura una vez que se complete la cadena de promesas.

Patrón de observador

En el patrón de observador, un sujeto tiene una lista de observadores dependientes. Cuando el sujeto entra en algún estado, los observadores son notificados de acuerdo con alguna interfaz. Los observadores pueden agregarse y eliminarse del sujeto. Estos observadores no existen en un vacío semántico, pero están esperando eventos para algún propósito.

Si este propósito ya no existe, los observadores deberían ser retirados del tema. Incluso en los idiomas recolectados de basura, esta eliminación podría tener que realizarse manualmente. Si no eliminamos un observador, se mantendrá vivo a través de la referencia del sujeto al observador, y con él todos los objetos a los que hace referencia el observador. Esto desperdicia memoria y degrada el rendimiento ya que el observador (ahora inútil) aún será notificado.

Las referencias débiles corrigen esta pérdida de memoria, ya que permiten que el observador sea recolectado como basura. Cuando el sujeto se da la vuelta para notificar a todos los observadores y encuentra que una de las referencias débiles a un observador está vacía, esa referencia se puede eliminar de forma segura. Alternativamente, las referencias débiles podrían implementarse de una manera que permita al sujeto registrar una devolución de llamada de limpieza que eliminará al observador al momento de la recolección.

Pero tenga en cuenta que las referencias débiles son solo una curita que limitan el daño al olvidar quitar un observador. La solución correcta sería asegurarse de que se elimine un observador cuando ya no sea necesario. Las opciones incluyen:

  • Hacerlo manualmente, pero es propenso a errores.

  • Usar algo parecido a probar con recursos en Java o usingen C #.

  • Destrucción determinista, como a través del idioma RAII. Tenga en cuenta que en un lenguaje con recolección de basura determinista, esto aún podría requerir referencias débiles del sujeto al observador para activar el destructor.

amon
fuente