Compartir a través de


Modelos de evento débil

En las aplicaciones, es posible que los controladores que están asociados a orígenes de eventos no se destruirán en coordinación con el objeto de escucha que adjuntó el controlador al origen. Esta situación puede provocar fugas de memoria. Windows Presentation Foundation (WPF) presenta un modelo de diseño que se puede usar para solucionar este problema, proporcionando una clase de administrador dedicada para eventos concretos e implementando una interfaz en los agentes de escucha para ese evento. Este modelo de diseño se conoce como modelo de eventos débil.

¿Por qué implementar el modelo de eventos débil?

La escucha de eventos puede provocar fugas de memoria. La técnica típica para escuchar un evento es usar la sintaxis específica del lenguaje que asocia un controlador a un evento en un origen. Por ejemplo, en C#, esa sintaxis es: source.SomeEvent += new SomeEventHandler(MyEventHandler).

Esta técnica crea una referencia segura desde el origen del evento al agente de escucha de eventos. Normalmente, adjuntar un controlador de eventos para un agente de escucha hace que el agente de escucha tenga una duración de objeto que está influenciada por la duración del objeto del origen (a menos que se quite explícitamente el controlador de eventos). Pero en determinadas circunstancias, es posible que quiera que la duración del objeto del agente de escucha se controle por otros factores, como si pertenece actualmente al árbol visual de la aplicación y no por la duración del origen. Cada vez que la duración del objeto de origen se extiende más allá de la duración del objeto del agente de escucha, el patrón de eventos normal provoca una fuga de memoria: el agente de escucha se mantiene activo más tiempo de lo previsto.

El modelo de eventos débil está diseñado para resolver este problema de fuga de memoria. El modelo de eventos débil se puede usar siempre que un agente de escucha necesite registrarse para un evento, pero el agente de escucha no sabe explícitamente cuándo anular el registro. El modelo de eventos débil también se puede usar siempre que la duración del objeto del origen supere la duración útil del objeto del agente de escucha. (En este caso, la utilidad la determina usted). El patrón de eventos débil permite que el cliente de escucha se registre y reciba el evento sin afectar a las características de duración del objeto del cliente de escucha de ninguna manera. En efecto, la referencia implícita del origen no determina si el agente de escucha es apto para la recolección de elementos no utilizados. La referencia es una referencia débil, de ahí la nomenclatura del modelo de eventos débil y las API relacionadas. El agente de escucha puede experimentar una recolección de elementos no utilizados o destruirse, y el origen puede continuar sin conservar referencias de controlador no recopilables a un objeto ahora destruido.

¿Quién debe implementar el modelo de eventos débil?

La implementación del modelo de eventos débil es interesante principalmente para los autores de controles. Como autor del control, usted es en gran medida responsable del comportamiento y la contención del control y el impacto que tiene en las aplicaciones en las que se inserta. Esto incluye el comportamiento de duración del objeto de control, en particular el control del problema de fuga de memoria descrito.

Ciertos escenarios se prestan inherentemente a la aplicación del modelo de eventos débil. Uno de estos escenarios es el enlace de datos. En el enlace de datos, es habitual que el objeto de origen sea completamente independiente del objeto de escucha, que es un destino de un enlace. Muchos aspectos del enlace de datos de WPF ya tienen el modelo de eventos débil aplicado en cómo se implementan los eventos.

Cómo implementar el modelo de eventos débil

Hay tres maneras de implementar un modelo de eventos débil. En la tabla siguiente se enumeran los tres enfoques y se proporcionan algunas instrucciones para cuándo debe usar cada uno.

Enfoque Cuándo implementar
Uso de una clase de administrador de eventos débil existente Si el evento al que quiere suscribirse tiene un objeto WeakEventManager correspondiente, use el administrador de eventos débil existente. Para obtener una lista de administradores de eventos débiles que se incluyen con WPF, consulte la jerarquía de herencia en la clase WeakEventManager. Dado que los administradores de eventos débiles incluidos son limitados, probablemente deba elegir uno de los otros enfoques.
Uso de una clase genérica de administrador de eventos débil Use un WeakEventManager<TEventSource,TEventArgs> genérico cuando un WeakEventManager existente no esté disponible, quiere una manera fácil de implementar y no le preocupa la eficiencia. El WeakEventManager<TEventSource,TEventArgs> genérico es menos eficaz que un administrador de eventos débil existente o personalizado. Por ejemplo, la clase genérica hace más reflexión para detectar el evento según el nombre del evento. Además, el código para registrar el evento mediante el WeakEventManager<TEventSource,TEventArgs> genérico es más detallado que usar un WeakEventManager existente o personalizado.
Creación de una clase personalizada de administrador de eventos débil Cree un WeakEventManager personalizado cuando un WeakEventManager existente no esté disponible y quiera la mejor eficiencia. El uso de un WeakEventManager personalizado para suscribirse a un evento será más eficaz, pero incurrirá en el costo de escribir más código al principio.
Uso de un administrador de eventos débil de terceros NuGet tiene varios administradores de eventos débiles y muchos marcos de WPF también admiten el patrón.

En las secciones siguientes se describe cómo implementar el modelo de eventos débil. En este tema, el evento al que suscribirse tiene las siguientes características.

  • El nombre del evento es SomeEvent.

  • La clase EventSource genera el evento.

  • El controlador de eventos tiene el tipo: SomeEventEventHandler (o EventHandler<SomeEventEventArgs>).

  • El evento pasa un parámetro de tipo SomeEventEventArgs a los controladores de eventos.

Uso de una clase de administrador de eventos débil existente

  1. Busque un administrador de eventos débil existente.

    Para obtener una lista de administradores de eventos débiles que se incluyen con WPF, consulte la jerarquía de herencia en la clase WeakEventManager.

  2. Use el nuevo administrador de eventos débil en lugar del enlace de eventos normal.

    Por ejemplo, si el código usa el siguiente modelo para suscribirse a un evento:

    source.SomeEvent += new SomeEventEventHandler(OnSomeEvent);
    

    Cámbielo por el siguiente modelo:

    SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);
    

    Del mismo modo, si el código usa el siguiente modelo para cancelar la suscripción de un evento:

    source.SomeEvent -= new SomeEventEventHandler(OnSomeEvent);
    

    Cámbielo por el siguiente modelo:

    SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);
    

Uso de la clase genérica de administrador de eventos débil

  1. Use la clase WeakEventManager<TEventSource,TEventArgs> genérica en lugar del enlace de eventos normal.

    Cuando se usa WeakEventManager<TEventSource,TEventArgs> para registrar agentes de escucha de eventos, proporcione el origen del evento y el tipo EventArgs como parámetros de tipo a la clase y llame a AddHandler como se muestra en el código siguiente:

    WeakEventManager<EventSource, SomeEventEventArgs>.AddHandler(source, "SomeEvent", source_SomeEvent);
    

Creación de una clase personalizada de administrador de eventos débil

  1. Copie la siguiente plantilla de clase en el proyecto.

    La clase hereda de la clase WeakEventManager.

    class SomeEventWeakEventManager : WeakEventManager
    {
    
        private SomeEventWeakEventManager()
        {
        }
    
        /// <summary>
        /// Add a handler for the given source's event.
        /// </summary>
        public static void AddHandler(EventSource source,
                                      EventHandler<SomeEventEventArgs> handler)
        {
            if (source == null)
                throw new ArgumentNullException("source");
            if (handler == null)
                throw new ArgumentNullException("handler");
    
            CurrentManager.ProtectedAddHandler(source, handler);
        }
    
        /// <summary>
        /// Remove a handler for the given source's event.
        /// </summary>
        public static void RemoveHandler(EventSource source,
                                         EventHandler<SomeEventEventArgs> handler)
        {
            if (source == null)
                throw new ArgumentNullException("source");
            if (handler == null)
                throw new ArgumentNullException("handler");
    
            CurrentManager.ProtectedRemoveHandler(source, handler);
        }
    
        /// <summary>
        /// Get the event manager for the current thread.
        /// </summary>
        private static SomeEventWeakEventManager CurrentManager
        {
            get
            {
                Type managerType = typeof(SomeEventWeakEventManager);
                SomeEventWeakEventManager manager =
                    (SomeEventWeakEventManager)GetCurrentManager(managerType);
    
                // at first use, create and register a new manager
                if (manager == null)
                {
                    manager = new SomeEventWeakEventManager();
                    SetCurrentManager(managerType, manager);
                }
    
                return manager;
            }
        }
    
        /// <summary>
        /// Return a new list to hold listeners to the event.
        /// </summary>
        protected override ListenerList NewListenerList()
        {
            return new ListenerList<SomeEventEventArgs>();
        }
    
        /// <summary>
        /// Listen to the given source for the event.
        /// </summary>
        protected override void StartListening(object source)
        {
            EventSource typedSource = (EventSource)source;
            typedSource.SomeEvent += new EventHandler<SomeEventEventArgs>(OnSomeEvent);
        }
    
        /// <summary>
        /// Stop listening to the given source for the event.
        /// </summary>
        protected override void StopListening(object source)
        {
            EventSource typedSource = (EventSource)source;
            typedSource.SomeEvent -= new EventHandler<SomeEventEventArgs>(OnSomeEvent);
        }
    
        /// <summary>
        /// Event handler for the SomeEvent event.
        /// </summary>
        void OnSomeEvent(object sender, SomeEventEventArgs e)
        {
            DeliverEvent(sender, e);
        }
    }
    
  2. Reemplace el nombre SomeEventWeakEventManager por su propio nombre.

  3. Reemplace los tres nombres descritos anteriormente por los nombres correspondientes para el evento. (SomeEvent, EventSource y SomeEventEventArgs)

  4. Establezca la visibilidad (pública, interna o privada) de la clase de administrador de eventos débil en la misma visibilidad que el evento que administra.

  5. Use el nuevo administrador de eventos débil en lugar del enlace de eventos normal.

    Por ejemplo, si el código usa el siguiente modelo para suscribirse a un evento:

    source.SomeEvent += new SomeEventEventHandler(OnSomeEvent);
    

    Cámbielo por el siguiente modelo:

    SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);
    

    Del mismo modo, si el código usa el siguiente patrón para cancelar la suscripción a un evento:

    source.SomeEvent -= new SomeEventEventHandler(OnSome);
    

    Cámbielo por el siguiente modelo:

    SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);
    

Vea también