C#开发WPF程序中的弱事件模式

 更新时间:2022年06月20日 10:46:46   作者:天方  
这篇文章介绍了C#开发WPF程序中的弱事件模式,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

在C#中,得益于强大的GC机制,使得我们开发程序变得非常简单,很多时候我们只需要管使用,而并不需要关心什么时候释放资源。但是,GC有的时并不是按照我们所期望的方式工作。

例如,我想实现一个在窗口的标题栏中实时显示当前的时间,一个比较常规的做法如下:

var timer = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(1) };
timer.Tick += (_s, _e) => this.Title = DateTime.Now.ToString();
timer.Start();

这种做法看起来非常简单而直接,它也确实能老老实实按照我们所设计的那样在窗口中实时显示并更新时间。但是,有经验的程序员们就知道,这里存在一个隐患:这个窗口永远不会释放。比较简单的验证方式是:手动关闭窗口,调用GC.Collect()函数,发现析构函数是不会调用的。

可能有的人会问了:不是有万能的GC嘛,为什么这个窗口不会释放?究其原因也非常简单,DispatchTimer的Tick事件中包含了对Window的引用,当窗口关闭时,DispatchTimer仍然在执行,因此Window就得不到释放。

知道了原因后,要解决也不难:在Window的关闭事件中,停止Timer的调用即可。这种方式确实行之有效,但显得不大优雅,感觉回到了要手动控制申请和释放的C语言年代,没有了GC自动管理下的"管杀不管埋"的便捷感觉。 那么,有没有一种我们只管使用,而不管释放的方案呢,答案就是弱事件模式

在弱事件模式下,事件委托只保留对象的弱引用,这样GC仍然能将该对象给回收掉。例如,对于上述代码,可以修改如下:

var timer = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(1) };
WeakEventManager<DispatcherTimer, EventArgs>.AddHandler(timer, "Tick", (_s, _e) => this.Title = DateTime.Now.ToString());
timer.Start();

由于Timer没有保存Window的强引用,当Windows关闭后,是会被GC回收掉的。

现在看起来没有什么问题了,不过,敏感的程序员们会发现,这里还存在一个隐患:DispatchTimer没有释放。虽然我们没有保存Timer的引用,但为了避免其被GC回收,内部仍然会维持其引用,必须显式停止。这里我们仍然可以利用弱事件模式,在感知到回调对象被释放时,手动停止Timer。要实现这个方法,必须我们实现自己的弱事件管理器: 

    public class DispatcherTimerManager : WeakEventManager
    {
        public static void Create(TimeSpan interval, EventHandler<EventArgs> handler)
        {
            var dispatcherTimer = new DispatcherTimer() { Interval = interval };
            DispatcherTimerManager.AddHandler(dispatcherTimer, handler);
            dispatcherTimer.Start();
        }

        public static void AddHandler(DispatcherTimer source, EventHandler<EventArgs> handler)
        {
            current.ProtectedAddHandler(source, handler);
        }

        public static void RemoveHandler(DispatcherTimer source, EventHandler<EventArgs> handler)
        {
            current.ProtectedRemoveHandler(source, handler);
        }

        static DispatcherTimerManager current;
        static DispatcherTimerManager()
        {
            current = new DispatcherTimerManager();
            SetCurrentManager(typeof(DispatcherTimerManager), current);
        }

        protected override ListenerList NewListenerList()
        {
            return new ListenerList<EventArgs>();
        }

        protected override void StartListening(object source)
        {
            var timer = (DispatcherTimer)source;
            timer.Tick += OnSomeEvent;
        }

        protected override void StopListening(object source)
        {
            var timer = (DispatcherTimer)source;
            timer.Tick -= OnSomeEvent;
            timer.Stop();
        }

        void OnSomeEvent(object sender, EventArgs e)
        {
            DeliverEvent(sender, e);
        }
    }

代码比较简单:当感知到回调对象被释放时,会执行StopListening函数我们只需要重写改函数,加入停止Timer操作即可。同样,我们也可以基于弱事件模式实现一个IObservable的自动管理类:

    public static class ObservableDispatcher
    {
        public static void AddHandler<T>(IObservable<T> source, EventHandler<DataEventArgs<T>> handler)
        {
            if ( Application.Current.Dispatcher != Dispatcher.CurrentDispatcher)
                throw new InvalidOperationException("需要在主线程上调用");

            AnymousDispatcher<T>.AddHandler(source, handler);
        }

        public static void RemoveHandler<T>(IObservable<T> source, EventHandler<DataEventArgs<T>> handler)
        {
            AnymousDispatcher<T>.RemoveHandler(source, handler);
        }


        class AnymousDispatcher<T> : WeakEventManager
        {
            public static void AddHandler(IObservable<T> source, EventHandler<DataEventArgs<T>> handler)
            {
                var wrapper = new ObservableEventWrapper<T>(source);
                current.ProtectedAddHandler(wrapper, handler);
            }

            public static void RemoveHandler(IObservable<T> source, EventHandler<DataEventArgs<T>> handler)
            {
                var wrapper = new ObservableEventWrapper<T>(source);
                current.ProtectedRemoveHandler(wrapper, handler);
            }

            static AnymousDispatcher<T> current;
            static AnymousDispatcher()
            {
                current = new AnymousDispatcher<T>();
                SetCurrentManager(typeof(AnymousDispatcher<T>), current);
            }

            protected override ListenerList NewListenerList()
            {
                return new ListenerList<DataEventArgs<T>>();
            }

            protected override void StartListening(object source)
            {
                var wrapper = source as ObservableEventWrapper<T>;
                wrapper.OnData += wrapper_OnData;
            }

            void wrapper_OnData(object sender, DataEventArgs<T> e)
            {
                DeliverEvent(sender, e);
            }

            protected override void StopListening(object source)
            {
                var wrapper = source as ObservableEventWrapper<T>;
                wrapper.OnData -= wrapper_OnData;
                wrapper.Dispose();
            }
        }

        class ObservableEventWrapper<T> : IDisposable
        {
            IDisposable disposeHandler;
            public ObservableEventWrapper(IObservable<T> dataSource)
            {
                disposeHandler = dataSource.Subscribe(onData);
            }

            void onData(T data)
            {
                OnData(this, new DataEventArgs<T>(data));
            }

            public event EventHandler<DataEventArgs<T>> OnData;

            public void Dispose()
            {
                disposeHandler.Dispose();
            }
        }
    }

限制:

弱事件模式非常有用,但不知道为什么微软将其限制在了WPF框架中了,从其实现上来看,应该是在UI线程上调用,但在MSDN上也没有找到其限制的说明。我试过在非UI线程上调用它,也是弱事件,但是不能触发StopListening函数。不知道这样有没有什么影响,但最好还是在UI线程上调用它。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • C#实现连接SQL Server2012数据库并执行SQL语句的方法

    C#实现连接SQL Server2012数据库并执行SQL语句的方法

    这篇文章主要介绍了C#实现连接SQL Server2012数据库并执行SQL语句的方法,结合实例形式较为详细的分析了C#连接SQL Server2012数据库并执行查询、插入等操作的相关实现技巧,需要的朋友可以参考下
    2017-10-10
  • C#模拟链表数据结构的实例解析

    C#模拟链表数据结构的实例解析

    这篇文章主要介绍了C#模拟链表数据结构的实例解析,包括队双向链表的模拟方法,例子中队链表的操作也有很好的说明,需要的朋友可以参考下
    2016-04-04
  • C#实现插入排序

    C#实现插入排序

    这篇文章介绍了C#实现插入排序的方法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-08-08
  • C# WebApi+Webrtc局域网音视频通话实例

    C# WebApi+Webrtc局域网音视频通话实例

    这篇文章主要为大家详细介绍了C# WebApi+Webrtc局域网音视频通话实例,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-07-07
  • 使用C#的aforge类库识别验证码实例

    使用C#的aforge类库识别验证码实例

    这篇文章主要介绍了使用C#的aforge类库识别验证码实例,aforge类库是一个非常强大的类库,包括计算机视觉与人工智能、图像处理、神经网络、遗传算法、机器学习、机器人等领域,需要的朋友可以参考下
    2014-08-08
  • C#窗体布局方式详解

    C#窗体布局方式详解

    这篇文章主要介绍了C#窗体布局方式详解的相关资料,需要的朋友可以参考下
    2016-09-09
  • C#中Entity Framework常见报错汇总

    C#中Entity Framework常见报错汇总

    给大家总结了C#中Entity Framework常见报错,以及处理这些错误的方法,希望能够为你提供到帮助。
    2017-11-11
  • DevExpress实现TreeList向上递归获取符合条件的父节点

    DevExpress实现TreeList向上递归获取符合条件的父节点

    这篇文章主要介绍了DevExpress实现TreeList向上递归获取符合条件的父节点,需要的朋友可以参考下
    2014-08-08
  • C#使用 Salt + Hash 来为密码加密

    C#使用 Salt + Hash 来为密码加密

    本文主要介绍了几种常见的破解密码的方法,为密码加盐(Salt)以及在.NET中的实现等。具有一定的参考价值,下面跟着小编一起来看下吧
    2017-01-01
  • 如何使用Dapper处理多个结果集与多重映射实例教程

    如何使用Dapper处理多个结果集与多重映射实例教程

    Dapper类是一个开源的数据库操作类,下面这篇文章主要给大家介绍了关于如何使用Dapper处理多个结果集与多重映射的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧
    2018-09-09

最新评论