非常好玩的C#/.NET 基礎(chǔ) -- 安全有效引發(fā)事件
最近在網(wǎng)上看到一篇很好的文章, 討論如何安全有效的引發(fā)事件.
也許你不一定要用到下面相同的解決方案, 但是至少你應(yīng)該知道在引發(fā)事件時(shí)候需要考慮的問題.
引發(fā)事件的問題
引發(fā)事件是一個(gè)非常容易的事情, 但是的確也有它的誤區(qū). 讓我們舉個(gè)例子. 假設(shè)我們寫個(gè)消息接收器, 每當(dāng)我們收到一個(gè)新消息, 我們引發(fā)一個(gè)包含了新消息的事件 MessageReceived.
安裝我們通常的方法,就是:
public class MessageReceivedEventArgs : EventArgs
{
// 接收到的消息
public string Message { get; private set; }
// 架構(gòu) ReceivedEventArgs
public MessageReceivedEventArgs(string message)
{
Message = message;
}
}
接下來, 我們創(chuàng)建一個(gè)非線程安全訪問的類UnsafeMessenger來實(shí)現(xiàn)這個(gè)消息同時(shí)通知所有的訂閱者(subscriber).
public class UnsafeMessenger
{
public event EventHandler<MessageReceivedEventArgs> MessageReceived;
// 當(dāng)收到新消息時(shí)調(diào)用
public void OnNewMessage(string message)
{
if (MessageReceived != null)
{
MessageReceived(this, new MessageReceivedEventArgs(message));
}
}
}
注意, 通常OnNewMessage() 是私有的, 但是在這里為了測(cè)試的方便,我們將它設(shè)為public.
大功告成!! 是嗎? 事實(shí)上, 如果我們是單線程的程序, 這的確已經(jīng)足夠, 但是這是非線程安全訪問(thread-safe).
為什么? 想想, 訂閱者可以任何時(shí)候訂閱或者取消訂閱. 比如,我們當(dāng)前有一個(gè)訂閱者, 那么當(dāng)接收到一個(gè)新消息,執(zhí)行到這一句時(shí):
if (MessageReceived != null)
肯定會(huì)通過, 因?yàn)橛幸粋€(gè)訂閱者, 如果這個(gè)時(shí)候, 這名訂閱者執(zhí)行了取消訂閱的命令:
myMessenger.MessageReceived -= MyMessageHandler;
那么MessageReceived委托 就為null 了,
//已經(jīng)通過了這個(gè)IF語句
if (MessageReceived != null)
{
//MessageReceived委托 就為null 了, 但是我們將要執(zhí)行這句
MessageReceived(this, new MessageReceivedEventArgs(message));
}
這個(gè)時(shí)候, 就會(huì)引發(fā)NullReferenceException.
方案一: 鎖住它, 鎖機(jī)制
當(dāng)允許多線程的時(shí)候, 我們可以用鎖機(jī)制來避免一個(gè)用戶在我們執(zhí)行事件時(shí)訂閱或者取消訂閱, 或者在用戶執(zhí)行操作時(shí), 不能引發(fā)事件.
public class SyncronizedMessenger : IMessenger
{
// 委托和鎖
private EventHandler<MessageReceivedEventArgs> _messageReceived;
private readonly object _raiseLock = new object();
// 訂閱/取消訂閱的鎖機(jī)制
public event EventHandler<MessageReceivedEventArgs> MessageReceived
{
add { lock (_raiseLock) { _messageReceived += value; } }
remove { lock (_raiseLock) { _messageReceived -= value; } }
}
// 引發(fā)事件的鎖機(jī)制
public void OnNewMessage(string message)
{
lock (_raiseLock)
{
if (_messageReceived != null)
{
_messageReceived(this, new MessageReceivedEventArgs(message));
}
}
}
}
方案二: 永不為空, 默認(rèn)加載一個(gè)訂閱者
我們面臨的主要問題是有可能委托為空. 那么如果事先加載一個(gè)委托,會(huì)怎么樣?
public class EmptySubscriberMessenger : IMessenger
{
// 立刻給它一個(gè)空的訂閱者
public event EventHandler<MessageReceivedEventArgs> MessageReceived = (s, e) => { };
// 現(xiàn)在根本無需檢查是否為 null!
public void OnNewMessage(string message)
{
MessageReceived(this, new MessageReceivedEventArgs(message));
}
}
方案三: 創(chuàng)建一個(gè)本地的委托副本
另外一個(gè)簡(jiǎn)單的方案, 也就是很多人都在使用的, 微軟建議的模式: 創(chuàng)建一個(gè)本地的委托副本.
public class LocalCopyMessenger : IMessenger
{
public event EventHandler<MessageReceivedEventArgs> MessageReceived;
// 當(dāng)我們引發(fā)事件時(shí), 做一個(gè)副本
public void OnNewMessage(string message)
{
var target = MessageReceived;
if (target != null)
{
target(this, new MessageReceivedEventArgs(message));
}
}
}
下面是以上四種方法的效率, 在執(zhí)行10億次的重復(fù)操作時(shí):
以上參考翻譯自: C#/.NET Fundamentals: Safely and Efficiently Raising Events
小結(jié)
有一種編程方式叫 Cargo Cult Programming, 中文名: 貨物崇拜編程. 維基定義為"
其特征為不明就里地儀式性地使用代碼或程序架構(gòu)。貨物崇拜編程通常是一個(gè)程序員既沒理解他要解決的 bug,也沒理解表面上的解決方案的典型表現(xiàn)。
這個(gè)名詞有時(shí)也指不熟練的或沒經(jīng)驗(yàn)的程序員從某處拷貝代碼到另一處,卻不太清楚其代碼是如何工作的,或者不清楚在新的地方是否需要這段代碼。也可以指不正確或過份的應(yīng)用設(shè)計(jì)模式,代碼風(fēng)格或編程方法,卻對(duì)其原理不明就里。"
我承認(rèn)在"高舉實(shí)用主義"(敝人的如何做一個(gè)快樂的ASP.NET程序員) 的年代, 為了效率, 我也經(jīng)常這樣做.--試問誰有時(shí)間給第三方控件做測(cè)試?
自從這個(gè)創(chuàng)建本地委托副本的方案被大牛們推薦后, 大家都在用, 有人也不一定明白它背后的故事.
有時(shí)間的朋友們聊聊.net中的野史, 談笑間擴(kuò)充一點(diǎn)編程的能力,總比聊哪個(gè)明星又被潛規(guī)則了要有益處. 哈哈~~~
本年總結(jié) + 新年祝福
有可能是本年最后一篇下面是我今天博文的部分列表, 先祝大家新的一年快樂!
* 程序員人生
* C# 語言
寫出優(yōu)雅簡(jiǎn)明代碼的論題集 -- Csharp(C#)篇[1]
寫出優(yōu)雅簡(jiǎn)明代碼的論題集 -- Csharp(C#)篇[2]
再說Csharp(C#) ”整潔代碼”那些事 -- 變小[1]
C# 中奇妙的函數(shù)–8. String Remove() 和 Replace()
C# 中奇妙的函數(shù)–7. String Split 和 Join
C# 中奇妙的函數(shù)–6. 五個(gè)序列聚合運(yùn)算(Sum, Average, Min, Max,Aggregate)
C# 中奇妙的函數(shù)–5. Nullable 靜態(tài)類
C# 中奇妙的函數(shù) -- 4. Empty, DefaultIfEmpty, Count
C# 中奇妙的函數(shù) -- 3. 聯(lián)接序列的五種簡(jiǎn)單方法
C# 中奇妙的函數(shù) -- 2. First 和 Single -- 你是她心中的第一還是唯一?
不可不知的C#基礎(chǔ) 4. 延遲加載 -- 提高性能
不可不知的C#基礎(chǔ) 2. -–從 struct 和 class的異同 說開去
不可不知的C#基礎(chǔ) 1. -- Extension 擴(kuò)展方法
* 其他
從 Comparison/Converter 到Func 的進(jìn)化
從 Linq Queries 快速生成數(shù)據(jù) HTML, EXCEL, CSV 報(bào)表
DynamicXml -- 動(dòng)態(tài)讀取操作XML (一個(gè)從XML到Object的通用實(shí)現(xiàn))


浙公網(wǎng)安備 33010602011771號(hào)