Weak Event Manager

问题

通过传统的方式监听事件(即C#的+=语法),有可能会导致内存泄漏,原因是事件源会持有对事件Handler所在对象的强引用从而阻碍GC回收它,这样事件handler对象的生命周期受到了事件源对象的影响。

解决方案

此问题有两个解决办法:1) 确保通过-=语法反注册事件处理器 2)使用弱事件模式(Weak Event Pattern)。本文主要讲解Weak Event Pattern。

在使用Weak Event Pattern时,主要涉及到两个类:WeakEventManager和IWeakEventListener。WeakEventManager的StartListening和StopListening函数可以被子类覆盖来进行事件监听的注册和反注册。IWeakEventListener只有下面一个函数,当事件触发时,WeakEventManager会调用该函数从而执行Handler的代码。

bool ReceiveWeakEvent (Type managerType, object sender, EventArgs e);

根据构造WeakEventManager的方法不同,可分为如下三种方式来应用Weak Event Pattern:

  1. 使用.Net Framework自带的Event Manager或者第三方库中的Event Manager(例如Prism)
  2. 使用泛型类WeakEventManager<EventSource, SomeEventEventArgs>
  3. 自己编写Event Manager

首先我们通过如下代码来看看传统的事件注册方式有什么问题,代码通过+=语法把listener.HandleEvent方法注册到了eventSource.CustomEvent事件上,在设置listener为null后并触发GC,但listener对象并不会被GC收集且会继续处理新的消息,图一是其运行结果。

    public static class ProblemInNormalRegister
    {
        public static void Test()
        {
            var eventSource = new CustomEventSource();
            var listener = new CustomEventListener();

            //register event listener
            eventSource.CustomEvent += listener.HandleEvent;

            //trigger event and listener.HandleEvent will be executed
            eventSource.Raise("First Message from CustomEventSource");

            //set listener to null
            listener = null;

            //trigger gc but the listener object will not collected.
            GCUtils.TriggerGC();

            //trigger event and listener.HandleEvent stil will be executed
            eventSource.Raise("Second Message from CustomEventSource");

            Console.Read();
        }
    }

接下来,让我们通过3种不同的方式来应用Weak Event Pattern来解决这个问题。

  1. 使用.Net Framework自带的Event Manager
    系统自带了一些像CollectionChangedEventManager,PropertyChangedEventManager的Weak Event Manager,每个事件管理器只处理一类事件,比如PropertyChangedEventManager是处理INotifyPropertyChanged.PropertyChanged事件的,完整列表请参考:https://docs.microsoft.com/en-us/dotnet/api/system.windows.weakeventmanager?view=netframework-4.8。 如果你要处理的事件恰好对应某个系统自带的Event Manager,那么你可以直接按照如下的方法拿来使用,其运行结果如图二所示,可以看到listener在被设置成null之后被回收了,从而也就不能再处理新的消息了
    public class UseExistingWeakEventManager_PropertyChangedEventManager
    {
        public static void Test()
        {
            var source = new PropertyChangedEventSource();
            var listener = new PropertyChangedEventEventListener();

            //Register listener to the source
            PropertyChangedEventManager.AddListener(source, listener, nameof(PropertyChangedEventSource.Name));

            //change property change and the ReceiveWeakEvent of PropertyChangedEventEventListener method will be called
            source.Name = "first name";

            //set listener to null so it will be ready for gc
            listener = null;

            //trigger gc and the listener will be collected.
            GCUtils.TriggerGC();

            //change property value but the ReceiveWeakEvent of PropertyChangedEventEventListener method will NOT be called
            source.Name = "second name"; 

            Console.Read();
        }
    }

  1. 使用泛型类WeakEventManager<EventSource, SomeEventEventArgs>
    按照如下代码使用.net framework的泛型类WeakEventManager,非常方便而且listener在设置为null之后可以被回收,运行结果如图三所示。
    public class GenericManagerTest
    {
        public static void Test()
        {
            var eventSource = new CustomEventSource();
            var listener = new CustomEventListener();

            //add handler listener.HandleEvent to CustomEvent of CustomEventSource
            WeakEventManager<CustomEventSource, CustomEventArg>.AddHandler(eventSource, "CustomEvent", listener.HandleEvent);

            //trigger event and listener.HandleEvent will be executed
            eventSource.Raise("First message");

            //set listener to null
            listener = null;

            //trigger gc but the listener object will be collected.
            GCUtils.TriggerGC();

            //trigger event and listener.HandleEvent stil will NOT be executed
            eventSource.Raise("Second Message");

            Console.Read();
        }
    }

  1. 自己编写Event Manager

首先,创建一个类如CustomizedWeakEventManager并继承自WeakEventManager,然后重写基类WeakEventManager中的StartListening和StopListening函数;此外CustomizedWeakEventManager也提供了AddListener和RemoveListener函数来方便添加和移除监听器,类的代码如下:

    public class CustomizedWeakEventManager : WeakEventManager
    {
        private static CustomizedWeakEventManager CurrentManager
        {
            get
            {
                CustomizedWeakEventManager manager = (CustomizedWeakEventManager)GetCurrentManager(typeof(CustomizedWeakEventManager));

                if (manager == null)
                {
                    manager = new CustomizedWeakEventManager();
                    SetCurrentManager(typeof(CustomizedWeakEventManager), manager);
                }

                return manager;
            }
        }

        public static void AddListener(CustomEventSource source, IWeakEventListener listener)
        {
            CurrentManager.ProtectedAddListener(source, listener);
        }

        public static void RemoveListener(CustomEventSource source, IWeakEventListener listener)
        {
            CurrentManager.ProtectedRemoveListener(source, listener);
        }

        protected override void StartListening(object source)
        {
            (source as CustomEventSource).CustomEvent += DeliverEvent;
        }

        protected override void StopListening(object source)
        {
            (source as CustomEventSource).CustomEvent -= DeliverEvent;
        }
    }

程序执行结果如下图:
和直接使用泛型类相比,自定义Event Manager性能要好一些。

完整的程序请参考:https://github.com/DerekLoveCC/Writings/blob/master/Blog/Code/AStockViewer/WeakEventPattern/WeakEventPattern.csproj

其他资源

  1. https://docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/weak-event-patterns
  2. https://www.codeproject.com/articles/738109/the-net-weak-event-pattern-in-csharp

原文地址:https://www.cnblogs.com/dereklovecc/p/11108286.html

时间: 2024-08-02 23:11:00

Weak Event Manager的相关文章

The .NET weak event pattern in C#

Introduction As you may know event handlers are a common source of memory leaks caused by the persistence of objects that are not used anymore, and you may think should have been collected, but are not, and for good reason. … Continue reading → Intro

Weak Event Patterns

https://msdn.microsoft.com/en-US/library/aa970850(v=vs.100).aspx In applications, it is possible that handlers that are attached to event sources will not be destroyed in coordination with the listener object that attached the handler to the source.

WPF: 深入理解 Weak Event 模型

在之前写的一篇文章(XAML: 自定义控件中事件处理的最佳实践)中,我们曾提到了在 .NET 中如果事件没有反注册,将会引起内存泄露.这主要是因为当事件源会对事件监听者产生一个强引用,导致事件监听者无法被垃圾回收. 在这篇文章中,我们首先将进一步说明内存泄露的问题:然后,我们会重点介绍 .NET 中的 Weak Event 模型以及它的应用:之所以使用 Weak Event 模型就是为了解决常规事件中所引起的内存泄露:最后,我们会自己来实现 Weak Event 模型. 一.再谈内存泄露 1.

Event Manager and Event Listener

?? 我已经读完关于事件的文件,看着一对夫妇的教程,但是还有一些我仍然不握.在我见过的Event Managers事件管理器示例,将触发该事件的方法是在同一个class 作为事件管理器.喜欢这个: using UnityEngine; using System.Collections; public class EventManager : MonoBehaviour { public delegate void CheckpointHandler(int id); public static

Event Managers

Some PLF-based controls expose a convenient facility for temporarily disabling their events and for checking if an event is being raised. The following sample code illustrates how it is used. using Infragistics.Win; using Infragistics.Win.UltraWinGri

C#中的弱事件(Weak Events in C#)

(原创翻译文章·转载请注明来源:http://blog.csdn.net/hulihui) 原文:Weak Events In C#: Different approaches to weak events. by Daniel Grunwald.  Download source code - 15.5 KB 翻译前序 翻译后记 目录 引言 究竟什么是事件? 第1部分:监听方(Listener-side)的弱事件 解决方案0:仅仅注销 解决方案1:事件调用后注销 解决方案2:带弱引用(Weak

storm事件管理器EventManager源码分析-event.clj

storm事件管理器定义在event.clj中,主要功能就是通过独立线程执行"事件处理函数".我们可以将"事件处理函数"添加到EventManager的阻塞队列中,EventManager的事件处理线程不断地从阻塞队列中获取"事件处理函数"并执行. EventManager协议 协议就是一组函数定义的集合,协议中函数的第一个参数必须为实现该协议的实例本身,类似于java中实例方法的第一个参数为this:协议类似于java中的接口. (defpro

C#学习日记24----事件(event)

事件为类和类的实例提供了向外界发送通知的能力,实现了对象与对象之间的通信,如果定义了一个事件成员,表示该类型具有 1.能够在事件中注册方法 (+=操作符实现). 2.能够在事件中注销方法(-=操作符实现). 3.当事件被触发时注册的方法会被通知(事件内部维护了一个注册方法列表).委托(Delegate)是事件(event)的载体,要定义事件就的要有委托.  有关委托的内容请点击 委托(De... www.mafengwo.cn/event/event.php?iid=4971258www.maf

Data Binding和INotifyPropertyChanged是如何协调工作的?

前言 WPF的一大基础就是Data Binding.在基于MVVM架构的基础上,只有通过实现INotifyPropertyChanged接口的ViewModel才能够用于Data Binding. 要实现INotifyPropertyChanged接口,只需要实现一个事件,event PropertyChangedEventHandler PropertyChange. delegate & event基础知识回顾 先来回顾下C#里delegate和event的基础知识. 我们知道在C#里,ev