WPF程序中的弱事件模式

简介: 原文:WPF程序中的弱事件模式在C#中,得益于强大的GC机制,使得我们开发程序变得非常简单,很多时候我们只需要管使用,而并不需要关心什么时候释放资源。但是,GC有的时并不是按照我们所期望的方式工作。 例如,我想实现一个在窗口的标题栏中实时显示当前的时间,一个比较常规的做法如下:     var...
原文: 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);
        }
    }
View Code

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

 1     public static class ObservableDispatcher
 2     {
 3         public static void AddHandler<T>(IObservable<T> source, EventHandler<DataEventArgs<T>> handler)
 4         {
 5             if ( Application.Current.Dispatcher != Dispatcher.CurrentDispatcher)
 6                 throw new InvalidOperationException("需要在主线程上调用");
 7 
 8             AnymousDispatcher<T>.AddHandler(source, handler);
 9         }
10 
11         public static void RemoveHandler<T>(IObservable<T> source, EventHandler<DataEventArgs<T>> handler)
12         {
13             AnymousDispatcher<T>.RemoveHandler(source, handler);
14         }
15 
16 
17         class AnymousDispatcher<T> : WeakEventManager
18         {
19             public static void AddHandler(IObservable<T> source, EventHandler<DataEventArgs<T>> handler)
20             {
21                 var wrapper = new ObservableEventWrapper<T>(source);
22                 current.ProtectedAddHandler(wrapper, handler);
23             }
24 
25             public static void RemoveHandler(IObservable<T> source, EventHandler<DataEventArgs<T>> handler)
26             {
27                 var wrapper = new ObservableEventWrapper<T>(source);
28                 current.ProtectedRemoveHandler(wrapper, handler);
29             }
30 
31             static AnymousDispatcher<T> current;
32             static AnymousDispatcher()
33             {
34                 current = new AnymousDispatcher<T>();
35                 SetCurrentManager(typeof(AnymousDispatcher<T>), current);
36             }
37 
38             protected override ListenerList NewListenerList()
39             {
40                 return new ListenerList<DataEventArgs<T>>();
41             }
42 
43             protected override void StartListening(object source)
44             {
45                 var wrapper = source as ObservableEventWrapper<T>;
46                 wrapper.OnData += wrapper_OnData;
47             }
48 
49             void wrapper_OnData(object sender, DataEventArgs<T> e)
50             {
51                 DeliverEvent(sender, e);
52             }
53 
54             protected override void StopListening(object source)
55             {
56                 var wrapper = source as ObservableEventWrapper<T>;
57                 wrapper.OnData -= wrapper_OnData;
58                 wrapper.Dispose();
59             }
60         }
61 
62         class ObservableEventWrapper<T> : IDisposable
63         {
64             IDisposable disposeHandler;
65             public ObservableEventWrapper(IObservable<T> dataSource)
66             {
67                 disposeHandler = dataSource.Subscribe(onData);
68             }
69 
70             void onData(T data)
71             {
72                 OnData(this, new DataEventArgs<T>(data));
73             }
74 
75             public event EventHandler<DataEventArgs<T>> OnData;
76 
77             public void Dispose()
78             {
79                 disposeHandler.Dispose();
80             }
81         }
82     }
View Code

限制:

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

    

目录
相关文章
|
12月前
|
C#
WPF防止程序多次运行
WPF防止程序多次运行
135 0
WPF界面无法正常显示(资源引用,如转换器),但程序正常运行
WPF界面无法正常显示(资源引用,如转换器),但程序正常运行
WPF界面无法正常显示(资源引用,如转换器),但程序正常运行
如何解决WPF中 ScrollViewer 内包含 TreeView 或者 ListBox 等控件时滚轮事件被劫持的问题
如何解决WPF中 ScrollViewer 内包含 TreeView 或者 ListBox 等控件时滚轮事件被劫持的问题
|
前端开发 C# Windows
WPF鼠标、键盘、拖拽事件、用行为封装事件
本文主要介绍了WPF中常用的鼠标事件、键盘事件以及注意事项,同时使用一个案例讲解了拓展事件。除此之外,本文还讲述如何用行为(Behavior)来封装事件。
|
SQL 前端开发 C#
WPF MVVM模式
WPF MVVM模式
|
C#
WPF程序 双击exe自动申请“以管理员方式运行”权限
原文:WPF程序 双击exe自动申请“以管理员方式运行”权限 实现方式: 在 xxx.exe 目录下包含其对应的清单文件(xxx.exe.manifest); 用记事本打开 manifest 文件,将文件中的项:更改为:
1320 0
|
C#
【msdn wpf forum翻译】如何在wpf程序(程序激活时)中捕获所有的键盘输入,而不管哪个元素获得焦点?
原文:【msdn wpf forum翻译】如何在wpf程序(程序激活时)中捕获所有的键盘输入,而不管哪个元素获得焦点?原文链接:http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/cf884a91-c135-447d-b16b-214d2d9e9972 有时有些特殊的程序需要这样处理。
953 0
|
C#
WPF 事件触发命令
原文:WPF 事件触发命令 方法一使用mvvmlight: xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.
1541 0
|
前端开发 C#
从PRISM开始学WPF(七)MVVM(三)事件聚合器EventAggregator?
原文:从PRISM开始学WPF(七)MVVM(三)事件聚合器EventAggregator? 从PRISM开始学WPF(一)WPF? 从PRISM开始学WPF(二)Prism? 从PRISM开始学WPF(三)Prism-Region? 从PRISM开始学WPF(四)Prism-Module? ...
1413 0
|
C# 开发框架
Prism for WPF再探(基于Prism事件的模块间通信)
原文:Prism for WPF再探(基于Prism事件的模块间通信) 上篇博文链接 Prism for WPF初探(构建简单的模块化开发框架) 一、简单介绍:   在上一篇博文中初步搭建了Prism框架的各个模块,但那只是搭建了一个空壳,里面的内容基本是空的,在这一篇我将实现各个模块间的通信,在上一篇博文的基础上改的。
1446 0