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文件中提供了大量的读写的配置,所以它是一种高效的程序配置方式,那么今天我就 ...
随机推荐
- Makefile编写记录
近期学习 Linux 需要使用 Makefile,网上搜罗了很多这方面的资料,所里在这里做一个整理. 1.静态模式 看一个例子: objects = foo.o bar.o all: $(object ...
- c# json 反序列化 泛型List 2行代码
List<EncyTable> list = new List<EncyTable>(); var jsonReqeust = "[{ENCY_ID:775,ENCY ...
- GYM - 101147 F.Bishops Alliance
题意: 一个n*n的棋盘,有m个主教.每个主教都有自己的权值p.给出一个值C,在棋盘中找到一个最大点集.这个点集中的点在同一条对角线上且对于点集中任意两点(i,j),i和j之间的主教数(包括i,j)不 ...
- [poj] 1236 networks of schools
原题 这是一道强连通分量板子题. 显然subtask1 是要输出入度为0的点的个数 而subtask2,我们考虑一下最优一定是把一个出度为零的点连到入度为零的点上,这样我们要输出的就是max(出度为零 ...
- hdu 5111 树上求交
hdu 5111 树上求交(树链剖分 + 主席树) 题意: 给出两棵树,大小分别为\(n1\),\(n2\), 树上的结点权值为\(weight_i\) 同一棵树上的结点权值各不相同,不同树上的结点权 ...
- 洛谷 P3573 [POI2014]RAJ-Rally 解题报告
P3573 [POI2014]RAJ-Rally 题意: 给定一个\(N\)个点\(M\)条边的有向无环图,每条边长度都是\(1\). 请找到一个点,使得删掉这个点后剩余的图中的最长路径最短. 输入输 ...
- Google C++编程风格指南 - 中文版
Google C++编程风格指南 - 中文版 from http://code.google.com/p/google-styleguide/ 版本: 3.133原作者: Benjy Weinberg ...
- 牛客~~wannafly挑战赛19~A 队列
链接:https://www.nowcoder.com/acm/contest/131/A来源:牛客网 题目描述 ZZT 创造了一个队列 Q.这个队列包含了 N 个元素,队列中的第 i 个元素用 Qi ...
- Codevs 1710 == POJ 1190 生日蛋糕 == 洛谷P1731
生日蛋糕 时间限制: 2 s 空间限制: 128000 KB 题目等级 : 大师 Master 题目描述 Description 7月17日是Mr.W的生日,ACM-THU为此要制作一个体积为Nπ ...
- [ CodeVS冲杯之路 ] P3145
不充钱,你怎么AC? 题目:http://codevs.cn/problem/3145/ 经典的汉诺塔问题 我们移动的时候,如果是最小的1号就可以直接移动,否则先将上面的x-1号先移动到借用塔上,然 ...