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线程上调用它。
WPF程序中的弱事件模式的更多相关文章
- 在WPF中应用弱事件模式
http://www.cnblogs.com/rickiedu/archive/2007/03/15/676021.html 在wpf中应用弱事件模式 感谢VS 的Intellisens ...
- 【转载】详细解读C#中的 .NET 弱事件模式
你可能知道,事件处理是内存泄漏的一个常见来源,它由不再使用的对象存留产生,你也许认为它们应该已经被回收了,但不是,并有充分的理由. 在这个短文中(期望如此),我会在 .Net 框架的上下文事件处理中展 ...
- C#中的 .NET 弱事件模式
引言 你可能知道,事件处理是内存泄漏的一个常见来源,它由不再使用的对象存留产生,你也许认为它们应该已经被回收了,但不是,并有充分的理由. 在这个短文中(期望如此),我会在 .Net 框架的上下文事件处 ...
- 在WPF程序中使用摄像头兼谈如何使用AForge.NET控件(转)
前言: AForge.NET 是用C#写的一个关于计算机视觉和人工智能领域的框架,它包括图像处理.神经网络.遗传算法和机器学习等.在C#程序中使用摄像头,我习惯性使用AForge.NET提供的类库.本 ...
- WPF 程序中启动和关闭外部.exe程序
当需要在WPF程序启动时,启动另一外部程序(.exe程序)时,可以按照下面的例子来: C#后台代码如下: using System; using System.Collections.Generic; ...
- 如何在WPF程序中使用ArcGIS Engine的控件
原文 http://www.gisall.com/html/47/122747-4038.html WPF(Windows Presentation Foundation)是美国微软公司推出.NET ...
- 如何追踪 WPF 程序中当前获得键盘焦点的元素并显示出来
原文:如何追踪 WPF 程序中当前获得键盘焦点的元素并显示出来 title: "如何追踪 WPF 程序中当前获得键盘焦点的元素并显示出来" publishDate: 2019-06 ...
- 解决WPF程序中ListBox ItemsSource变化时不重置ScrollBar的问题
解决WPF程序中ListBox ItemsSource变化时不重置ScrollBar的问题 当我们改变ListBox的ItemsSource时,会发现这样一个问题:数据源变化时,虽然控件中的内容会跟着 ...
- WPF程序中App.Config文件的读与写
WPF程序中的App.Config文件是我们应用程序中经常使用的一种配置文件,System.Configuration.dll文件中提供了大量的读写的配置,所以它是一种高效的程序配置方式,那么今天我就 ...
随机推荐
- 2 25urllib.py
""" urllib.request.urlopen(url,data,timeout) """ # from urllib.request ...
- HDU 4027 Can you answer these queries(线段树 + 观察 )
这题主要考察观察能力. 2^63最多只需要开7次根号就会变成1,当数字变成1之后就不需要再对其进行操作. 对于含有大于1数字的区间,向下更新. 对于数字全为1的区间,直接返回. #include &l ...
- 软工实践 - 第十四次作业 Alpha 冲刺 (5/10)
队名:起床一起肝活队 组长博客:https://www.cnblogs.com/dawnduck/p/9992094.html 作业博客:班级博客本次作业的链接 组员情况 组员1(队长):白晨曦 过去 ...
- HTTPS和HTTP的区别:
https协议需要到ca申请证书,一般免费证书很少,需要交费.http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议http和https使用的是完全不同的连接方式用的 ...
- hdu1007 平面最近点对(暴力+双线程优化)
突发奇想,用双线程似乎可以优化一些暴力 比如说平面最近点对这个题目,把点复制成2份 一份按照x排序,一份按照y排序 然后双线程暴力处理,一份处理x,一份处理y 如果数据利用x递减来卡,那么由于双线程, ...
- 一文看懂Kafka消息格式的演变
摘要 对于一个成熟的消息中间件而言,消息格式不仅关系到功能维度的扩展,还牵涉到性能维度的优化.随着Kafka的迅猛发展,其消息格式也在不断的升级改进,从0.8.x版本开始到现在的1.1.x版本,Kaf ...
- 《c程序设计语言》读书笔记-4.1-判断字符串在另一个字符串中的位置
#include <io.h> #include <stdio.h> #include <string.h> #include <stdlib.h> # ...
- html状态码
100——客户必须继续发出请求101——客户要求服务器根据请求转换HTTP协议版本 200——交易成功201——提示知道新文件的URL202——接受和处理.但处理未完成203——返回信息不确定或不完整 ...
- 我对 aspnetpager和repeater以及查询条件的封装
/// <summary> /// 绑定所有图片列表 /// </summary> /// <param name=&qu ...
- 《Linux命令、编辑器与shell编程》第三版 学习笔记---000
Linux概述 1.具有内核编程接口 2.支持多用户(同时) 3.支持多任务 4.支持安全的分层文件系统 a.标准 b.链接 c.权限 5.shell(命令解释器和编程语言) a.文件名生成(通配符和 ...