博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
WPF程序中的弱事件模式
阅读量:5908 次
发布时间:2019-06-19

本文共 5146 字,大约阅读时间需要 17 分钟。

原文:

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

限制:

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

    

转载地址:http://xwtpx.baihongyu.com/

你可能感兴趣的文章
常见JVM内存异常分析
查看>>
针对不同浏览器内核css写法
查看>>
ubuntu下添加gimp的ppa
查看>>
使用Java8实现自己的个性化搜索引擎
查看>>
龙家贰少的MarkDown学习笔记
查看>>
查看端口占用命令
查看>>
arm9时钟及定时器
查看>>
vi 常用命令
查看>>
抓包工具Charles
查看>>
码云项目100,水一发
查看>>
CLRS 4.2 Exercises
查看>>
redis启动警告处理
查看>>
【python初级】010-构造方法,属性和迭代器
查看>>
Textillate.js – 实现动感的 CSS3 文本动画的简单插件(用法详情&只支持现代浏览)...
查看>>
项目 调dubbo接口 异常总结
查看>>
通过Gearman实现MySQL到Redis的数据复制
查看>>
基于swoole扩展的异步redis客户端
查看>>
仿webqq桌面–jquery desktop 2.0
查看>>
数据库与图片完美解决方案
查看>>
Peer certificate cannot be authenticated with known CA certificates
查看>>