当前位置: 首页 > news >正文

引发事件的问题

引发事件是一个非常容易的事情, 但是的确也有它的误区. 让我们举个例子. 假设我们写个消息接收器, 每当我们收到一个新消息, 我们引发一个包含了新消息的事件MessageReceived.

安装我们通常的方法,就是:

public class MessageReceivedEventArgs : EventArgs { // 接收到的消息 public string Message { get; private set; } // 架构 ReceivedEventArgs public MessageReceivedEventArgs(string message) { Message = message; } }

接下来, 我们创建一个非线程安全访问的类UnsafeMessenger来实现这个消息同时通知所有的订阅者(subscriber).

public class UnsafeMessenger { public event EventHandler<MessageReceivedEventArgs> MessageReceived; // 当收到新消息时调用 public void OnNewMessage(string message) { if (MessageReceived != null) { MessageReceived(this, new MessageReceivedEventArgs(message)); } } }

注意, 通常OnNewMessage()是私有的, 但是在这里为了测试的方便,我们将它设为public.

大功告成!! 是吗? 事实上, 如果我们是单线程的程序, 这的确已经足够, 但是这是非线程安全访问(thread-safe).

为什么? 想想, 订阅者可以任何时候订阅或者取消订阅. 比如,我们当前有一个订阅者, 那么当接收到一个新消息,执行到这一句时:

if (MessageReceived != null)
肯定会通过, 因为有一个订阅者, 如果这个时候, 这名订阅者执行了取消订阅的命令:
myMessenger.MessageReceived -= MyMessageHandler;
那么MessageReceived委托 就为null 了,
//已经通过了这个IF语句 if (MessageReceived != null) { //MessageReceived委托 就为null 了, 但是我们将要执行这句 MessageReceived(this, new MessageReceivedEventArgs(message)); }

这个时候, 就会引发NullReferenceException.

方案一: 锁住它, 锁机制

当允许多线程的时候, 我们可以用锁机制来避免一个用户在我们执行事件时订阅或者取消订阅, 或者在用户执行操作时, 不能引发事件.

public class SyncronizedMessenger : IMessenger { // 委托和锁 private EventHandler<MessageReceivedEventArgs> _messageReceived; private readonly object _raiseLock = new object(); // 订阅/取消订阅的锁机制 public event EventHandler<MessageReceivedEventArgs> MessageReceived { add { lock (_raiseLock) { _messageReceived += value; } } remove { lock (_raiseLock) { _messageReceived -= value; } } } // 引发事件的锁机制 public void OnNewMessage(string message) { lock (_raiseLock) { if (_messageReceived != null) { _messageReceived(this, new MessageReceivedEventArgs(message)); } } } }

这样, 如果有人试图订阅或取消订阅时, 必须要等待OnNewMessage事件的完成, 反之亦然.

方案二: 永不为空, 默认加载一个订阅者

我们面临的主要问题是有可能委托为空. 那么如果事先加载一个委托,会怎么样?

public class EmptySubscriberMessenger : IMessenger { // 立刻给它一个空的订阅者 public event EventHandler<MessageReceivedEventArgs> MessageReceived = (s, e) => { }; // 现在根本无需检查是否为 null! public void OnNewMessage(string message) { MessageReceived(this, new MessageReceivedEventArgs(message)); } }

方案三: 创建一个本地的委托副本

另外一个简单的方案, 也就是很多人都在使用的, 微软建议的模式: 创建一个本地的委托副本.

public class LocalCopyMessenger : IMessenger { public event EventHandler<MessageReceivedEventArgs> MessageReceived; // 当我们引发事件时, 做一个副本 public void OnNewMessage(string message) { var target = MessageReceived; if (target != null) { target(this, new MessageReceivedEventArgs(message)); } } }

下面是以上四种方法的效率, 在执行10亿次的重复操作时:

以上参考翻译自: C#/.NET Fundamentals: Safely and Efficiently Raising Events

小结

有一种编程方式叫 Cargo Cult Programming, 中文名: 货物崇拜编程. 维基定义为"

其特征为不明就里地仪式性地使用代码或程序架构。货物崇拜编程通常是一个程序员既没理解他要解决的 bug,也没理解表面上的解决方案的典型表现。

这个名词有时也指不熟练的或没经验的程序员从某处拷贝代码到另一处,却不太清楚其代码是如何工作的,或者不清楚在新的地方是否需要这段代码。也可以指不正确或过份的应用设计模式,代码风格或编程方法,却对其原理不明就里。"

我承认在"高举实用主义"(敝人的如何做一个快乐的ASP.NET程序员) 的年代, 为了效率, 我也经常这样做.--试问谁有时间给第三方控件做测试?

自从这个创建本地委托副本的方案被大牛们推荐后, 大家都在用, 有人也不一定明白它背后的故事.

有时间的朋友们聊聊.net中的野史, 谈笑间扩充一点编程的能力,总比聊哪个明星又被潜规则了要有益处. 哈哈~~~

本年总结 + 新年祝福

有可能是本年最后一篇下面是我今天博文的部分列表, 先祝大家新的一年快乐!

* 程序员人生

有些时候,作为程序员,我们只是需要被重启一下

码斗士的修炼之路 -- 如何保持并提升战斗力

如何做一个快乐的ASP.NET程序员

* C# 语言

写出优雅简明代码的论题集 -- Csharp(C#)篇[1]

写出优雅简明代码的论题集 -- Csharp(C#)篇[2]

再说Csharp(C#) ”整洁代码”那些事 -- 变小[1]

C# 中奇妙的函数–8. String Remove() 和 Replace()

C# 中奇妙的函数–7. String Split 和 Join

C# 中奇妙的函数–6. 五个序列聚合运算(Sum, Average, Min, Max,Aggregate)

C# 中奇妙的函数–5. Nullable 静态类

C# 中奇妙的函数 -- 4. Empty, DefaultIfEmpty, Count

C# 中奇妙的函数 -- 3. 联接序列的五种简单方法

C# 中奇妙的函数 -- 2. First 和 Single -- 你是她心中的第一还是唯一?

C# 中奇妙的函数 -- 1. ToLookup

不可不知的C#基础 4. 延迟加载 -- 提高性能

不可不知的C#基础 3. 线程浅析

不可不知的C#基础 2. -–从 struct 和 class的异同 说开去

不可不知的C#基础 1. -- Extension 扩展方法

http://www.jsqmd.com/news/1126146/

相关文章:

  • WhatWeb:1800 多个插件,扫一眼就知道网站用了什么技术
  • Windows AirPlay 2投屏终极实战:3步构建跨平台屏幕镜像系统
  • ForgeGradle 7:Minecraft模组构建的现代化演进与架构解析
  • 家庭档案数字化:OCR技术应用与实战技巧
  • 国家中小学智慧教育平台电子课本下载工具:三步解锁海量教育资源宝库
  • 国内量化软件推荐怎么选:先拆研究回测和盯盘边界
  • 2026年常州二手车选购全攻略:热门车型盘点与避坑技巧解析
  • AntiDupl终极指南:三步快速清理电脑重复照片,释放宝贵磁盘空间
  • SmartTable v1.5.2 发布 —— 合并单元格、全局搜索与行列冻结
  • 国家中小学智慧教育平台电子课本下载完整教程:快速获取PDF教材资源
  • AI+Playwright:零编码实现Web自动化测试的完整实践指南
  • 2026年专升本论文降AI攻略:专升本毕业论文AIGC超标4.8元达标完整方案
  • DTLN 模型 TensorFlow 2.x 实战:32ms 帧长优化,PESQ 提升至 3.11(附 TFLite 量化)
  • 5分钟掌握:国家中小学智慧教育平台电子课本下载的终极解决方案
  • 2G显存跑通LLM全流程:大模型白盒子构建指南
  • 3步快速清理重复图片:AntiDupl智能去重工具使用指南
  • 一款用.net core实现的BI工具
  • Midscene.js跨平台自动化测试架构深度解析:视觉AI驱动的高效测试解决方案
  • 终极指南:3步解决群晖DSM 7.2.2 Video Station不兼容问题
  • AntiDupl图片去重工具完整指南:智能清理重复照片,高效释放磁盘空间
  • 【计算机毕业设计】旧物循环利用管理小程序的安全开发与实现
  • 3步轻松解除Cursor AI试用限制:免费享受Pro功能的完整指南
  • 3分钟快速解锁MobaXterm专业版:免费许可证生成器完整指南
  • 3步解锁索尼相机隐藏功能:OpenMemories-Tweak完整使用指南
  • 新电商智能分析决策系统——基于Streamlit+随机森林的电商数据全链路分析平台
  • 【学习记录】Week12(三):House of Corrosion——global_max_fast 的越界狂飙
  • 终极免费重复文件清理工具:dupeGuru让你的磁盘空间瞬间翻倍
  • 开源Word文档差异分析工具:提升团队协作效率的实践指南
  • AI智能体开发指南:从入门到实战应用
  • 第三方平台信息不一致,企业说明材料该怎么写才稳?