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文件中提供了大量的读写的配置,所以它是一种高效的程序配置方式,那么今天我就 ...
随机推荐
- iptables的配置文件/etc/sysconfig/iptables不存在 linux防火墙开关命令
某linux服务器,使用 cat /etc/sysconfig/iptables命令时, 找不到文件. 1. service iptables status 使用该命令检查状态 如果之前找不到配置文件 ...
- [ecmagent][redis学习][1初识redis] python操作redis
#1 连接redis # 连接redis -- import redis -- 使用端口连接redis conn = redis.Redis(host=) -- 使用套接字连接 r = redis.R ...
- HDU 4731 Minimum palindrome (找规律)
M=1:aaaaaaaa…… M=2:DFS+manacher, 暴出N=1~25的最优解,找规律.N<=8的时候直接输出,N>8时,头两个字母一定是aa,剩下的以aababb循环,最后剩 ...
- FileZilla修改文件大小格式
之前使用FileZilla觉得很奇怪,文件大小显示为字节格式,很不习惯. 最近发现是在这里修改. 菜单“编辑”下选择设置,选择选项“文件大小格式”,在右边选择为:“使用SI式二进制前缀.(如 1 KB ...
- ArcGIS 创建要素时提示“表已经被注册(table already registered)”
今天一位实施大哥在ArcCatalog中要重建一个要素类,所以就在ArcCatalog中连接上Oracle数据库,直接删除了要素类,然后重新创建了一个新的要素类,可是却报错“表已经被创建”,并提示不可 ...
- 想玩API,这些套路我来告诉你!
小伙伴是不是时常听说各种api接口的问题呢,可能许多人第一感觉:那是什么个玩意儿,那么多人回去研究它,今天思梦PHP小编就来为你揭开他的神秘的面纱,先看一下百度百科上面的官方的解释: 其实说白了就是为 ...
- Python中的多线程编程,线程安全与锁(二)
在我的上篇博文Python中的多线程编程,线程安全与锁(一)中,我们熟悉了多线程编程与线程安全相关重要概念, Threading.Lock实现互斥锁的简单示例,两种死锁(迭代死锁和互相等待死锁)情况及 ...
- CF911F Tree Destruction 解题报告
CF911F Tree Destruction 题意翻译 给你一棵树,每次挑选这棵树的两个叶子,加上他们之间的边数(距离),然后将其中一个点去掉,问你边数(距离)之和最大可以是多少. 输入输出格式 输 ...
- College student reflects on getting started in open source(二)
My budding interest grew into a full-time obsession: creating artwork on my clunky, laggy laptop. 我萌 ...
- uoj Goodbye Dingyou Round 题解
2.14 晚上的比赛, 现在改好了四题, 还差提答. 在这补个题解 新年的xor Description 给你 \(n\) , 然后要你构造 \([L, R], L<R\) 使得区间异或和为 \ ...