C#開發(fā)WPF程序中的弱事件模式
在C#中,得益于強大的GC機制,使得我們開發(fā)程序變得非常簡單,很多時候我們只需要管使用,而并不需要關心什么時候釋放資源。但是,GC有的時并不是按照我們所期望的方式工作。
例如,我想實現(xiàn)一個在窗口的標題欄中實時顯示當前的時間,一個比較常規(guī)的做法如下:
var?timer =?new?DispatcherTimer() { Interval =?TimeSpan.FromSeconds(1) }; timer.Tick += (_s, _e) =>?this.Title =?DateTime.Now.ToString(); timer.Start();
這種做法看起來非常簡單而直接,它也確實能老老實實按照我們所設計的那樣在窗口中實時顯示并更新時間。但是,有經(jīng)驗的程序員們就知道,這里存在一個隱患:這個窗口永遠不會釋放。比較簡單的驗證方式是:手動關閉窗口,調(diào)用GC.Collect()函數(shù),發(fā)現(xiàn)析構(gòu)函數(shù)是不會調(diào)用的。
可能有的人會問了:不是有萬能的GC嘛,為什么這個窗口不會釋放?究其原因也非常簡單,DispatchTimer的Tick事件中包含了對Window的引用,當窗口關閉時,DispatchTimer仍然在執(zhí)行,因此Window就得不到釋放。
知道了原因后,要解決也不難:在Window的關閉事件中,停止Timer的調(diào)用即可。這種方式確實行之有效,但顯得不大優(yōu)雅,感覺回到了要手動控制申請和釋放的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回收掉的。
現(xiàn)在看起來沒有什么問題了,不過,敏感的程序員們會發(fā)現(xiàn),這里還存在一個隱患:DispatchTimer沒有釋放。雖然我們沒有保存Timer的引用,但為了避免其被GC回收,內(nèi)部仍然會維持其引用,必須顯式停止。這里我們?nèi)匀豢梢岳萌跏录J?,在感知到回調(diào)對象被釋放時,手動停止Timer。要實現(xiàn)這個方法,必須我們實現(xiàn)自己的弱事件管理器:
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); } }
代碼比較簡單:當感知到回調(diào)對象被釋放時,會執(zhí)行StopListening函數(shù)我們只需要重寫改函數(shù),加入停止Timer操作即可。同樣,我們也可以基于弱事件模式實現(xiàn)一個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("需要在主線程上調(diào)用"); 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框架中了,從其實現(xiàn)上來看,應該是在UI線程上調(diào)用,但在MSDN上也沒有找到其限制的說明。我試過在非UI線程上調(diào)用它,也是弱事件,但是不能觸發(fā)StopListening函數(shù)。不知道這樣有沒有什么影響,但最好還是在UI線程上調(diào)用它。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
C#實現(xiàn)連接SQL Server2012數(shù)據(jù)庫并執(zhí)行SQL語句的方法
這篇文章主要介紹了C#實現(xiàn)連接SQL Server2012數(shù)據(jù)庫并執(zhí)行SQL語句的方法,結(jié)合實例形式較為詳細的分析了C#連接SQL Server2012數(shù)據(jù)庫并執(zhí)行查詢、插入等操作的相關實現(xiàn)技巧,需要的朋友可以參考下2017-10-10C#模擬鏈表數(shù)據(jù)結(jié)構(gòu)的實例解析
這篇文章主要介紹了C#模擬鏈表數(shù)據(jù)結(jié)構(gòu)的實例解析,包括隊雙向鏈表的模擬方法,例子中隊鏈表的操作也有很好的說明,需要的朋友可以參考下2016-04-04C# WebApi+Webrtc局域網(wǎng)音視頻通話實例
這篇文章主要為大家詳細介紹了C# WebApi+Webrtc局域網(wǎng)音視頻通話實例,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-07-07DevExpress實現(xiàn)TreeList向上遞歸獲取符合條件的父節(jié)點
這篇文章主要介紹了DevExpress實現(xiàn)TreeList向上遞歸獲取符合條件的父節(jié)點,需要的朋友可以參考下2014-08-08如何使用Dapper處理多個結(jié)果集與多重映射實例教程
Dapper類是一個開源的數(shù)據(jù)庫操作類,下面這篇文章主要給大家介紹了關于如何使用Dapper處理多個結(jié)果集與多重映射的相關資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面隨著小編來一起學習學習吧2018-09-09