补充有两个:

  1. 一个是系列(五)中讲到的事件编程(网址链接),该文提及到了事件编程的几种方式以及容易引起的一些异常,本文补充“多线程事件编程”这一块。
  2. 第二个是前三篇博客中提及到的“泵”结构在编程中的应用,我稍微做一点补充。

总结有一个:

  1. 如果您善于总结和类比,您会发现世界好多东西其实都是一样的。这部分主要理清楚框架时代中的框架和我们coder所写代码之间的关联。

下面是正文:

多线程事件编程

系列(五)中提及到了事件在注册和注销时,系统已经做了多线程处理,只是不太完美(以this为锁对象,this是public的,锁对象是不能对外公开的),后来通过自己定义锁对象加锁来实现的。可是该篇文章并没有提到在类内部激发事件时可能引发的异常:

 class Subject
{
XXEventHandler _xx;
object _xxSync = new Object();
public event XXEventHandler XX
{
add
{
lock(_xxSync)
{
_xx = (XXEventHandler)Delegate.Combine(_xx,value);
}
}
remove
{
lock(_xxSync)
{
_xx = (XXEventHandler)Delegate.Remove(_xx,value);
}
}
}
protected virtual void OnXX(XXEventArgs e)
{
if(_xx != null)
{
_xx(this,e);
}
}
public void DoSomething()
{
// …
OnXX(new XXEventArgs(…));
}
}

如上代码所述,在多线程情况下,if(_xx != null)这行代码执行为true后,在执行下一行_xx(this,e);之前,_xx可能已经为null,引发异常理所当然。解决方法很简单,照葫芦画瓢,在OnXX中加锁,源代码变为:

     protected virtual void OnXX(XXEventArgs e)
{
lock(_xxSync)
{
if(_xx != null)
{
_xx(this,e);
}
}
}

没错,这样确实能解决激发事件时有可能引发的异常,但如果仅仅是为了说明该方法可以解决问题的话,我是不会特大篇幅来说明它的。我们来看另外一种巧妙解决方法:

     protected virtual void OnXX(XXEventArgs e)
{
XXEventHandler xx = _xx;
if(xx != null)
{
xx(this,e);
}
}

如上代码所述,在判断_xx是否为null之前,我们先用一个临时变量代替它,之后将使用_xx的地方全部替换为xx。这样,就不用担心xx会由其它线程改变为null了,因为xx对其他线程不可见。这个原理很简单,委托链是不可改变的(Delegates is immutable),也就是说,我们注册或者注销事件时,并不是在原来的委托链表基础上进行增加或者删除节点,而是每次都是重新生成了一个全新链表再赋给委托变量。其实这个诸位可以找到规律,我们在注册注销事件时,一般obj.Event+=…或者Event = Delegate.Combine(…) Event = Delegate.Remove(),可以看出,每次都是将一个全新的值赋给原来委托变量,并没有在原来链表基础上进行操作,因此,_xx和xx虽然同是指向同一链表,但是我们注销注册事件时,只是让_xx指向另外一个链表而已,原链表(xx)并没有变。

这个其实就是我们刚学习编程的时候,使用值传递调用方法时,实参将值传递给了形参,形参如果改变了(被重新赋值),实参的值是不会变的。指针(引用)也一样,形参指向了另外一个对象,实参还是指向原来的对象。

“泵”结构的另外一种方式

系列(十三)中(网址链接)讲到,在泵结构中,如果在获取数据环节直接处理数据容易降低获取数据的效率,也就是说,最好不要一获取到数据就处理它,因为处理数据大多数情况下是一个耗时过程,数据处理结束前,下一次“数据获取”不能开始,影响获取数据的效率。如下图:

图1

如图所示,处理数据在泵循环体内,数据处理结束之前,缓冲区中的数据就会大量积累。我们当时的做法是,获取数据后不马上进行分析处理,而是先将数据写入一个有序缓冲区,然后另创建“泵”去分析处理这些数据,这样一来,不会影响数据获取环节的效率。因此,诸位可以看见有三个“泵”(数据接收,数据分析,数据处理)联合工作。

事实上,太多“泵”协作工作也是会影响整个系统效率的,这就像多个人协同工作,虽然人多力量大,但是人多需要考虑同步共享资源、人跟人之间的协作能力等情况,这个好比“生产者消费者模式”,

图2

当“生产者-缓冲区-消费者”这一结构过多时,数据从接收到最终被处理,是需要一个漫长的过程,因此,我们需要寻找一个平衡点。有两种改进方式:

1)

图3

如上图,接收数据后,直接开启异步分析和处理过程。

2)

图4

如上图,数据接收后,将其写入缓冲区,然后另外再开启线程分析和处理数据,这个就把“数据分析”和“数据处理”合并在一块了。这两个严格来说耦合度比原来那个要高。

注:在通讯编程中,图3适合UDP通信,因为UDP每次接受到的数据都是一个完整的数据包,数据接收后直接开始分析处理,图4适合TCP通信,因为TCP传输数据是以“流”格式传输的,并且每次接收到的数据不一定是完整的,我们必须先将接收到的数据按顺序写入一个有序的缓冲区中,然后再从缓冲区中提取完整的数据进行分析处理。

框架与客户端代码之间的关系

总结这个的主要原因是上次在网上看见有一个人问,使用基类引用指向一个派生类实例时,为什么不能通过该引用访问派生类中使用new关键字覆盖基类的方法,而只能访问到基类中的方法。我看了他给出的实例代码,发现其实根本就没必要使用基类引用去指向派生类实例,纯属滥用。是的,好多时候我们不知道为什么要那么使用,只因为我们看见别人那样用过,代码:

 class People
{
string _name;
string _sex;
// …
public void Info()
{
ShowInfo();
}
protected virtual void ShowInfo()
{
Console.WriteLine(“基本信息 姓名:”+_name+” 性别:”+_sex);
}
}
class Student:People
{
//…
protected override void ShowInfo()
{
base.ShowInfo();
Console.WriteLine(“附加信息 职业:学生”);
}
} class Teacher:People
{
//…
protected override void ShowInfo()
{
base.ShowInfo();
Console.WriteLine(“附加信息 职业:教师”);
}
}

以上三个类型,现在假设我要输出某一类型对象的信息,该怎么写?

public void Func(People p)

{

p.Info();

}

这是大多数人的写法,理由很简单,它既可以输出Student的信息也可以输出Teacher的信息,确实是这样的,但是当你确定要输出信息的对象类型时(而且很多时候属于这种情况),是没必要这样写的,比如你确定要输出信息的对象类型为Student,那么你完全可以这样:

public void Func(Student s)

{

s.Info();

}

我真不明白为什么你明明非常确定要使用哪个类型,却偏偏要用基类引用代替派生类引用,就是因为大家常说的“依赖于抽象而非具体”吗?这个话没错,但要看场合,当你不确定要使用哪个类型时,你可以用一个抽象引用(基类引用),当你已经非常确定了使用哪个类型时,你就没必要再去使用一个抽象引用了,直接使用具体引用(派生类引用)。抽象引用能完成的东西,具体引用都能做到,反过来却不成立,如果Student类中有一个public DoHomework(),你能用People类型的引用去访问它吗?你根本不能。

因此,可以很大胆地说,“依赖于抽象而非具体”是一个迫不得已的结论,如果编程世界里没有那么多的不确定,完全不需要这个结论,谁会去使用一个不确定性的东西呢?可是,事实上编程世界里有太多的不确定,表现最为明显的就是框架中,之所以框架中有那么多的不确定性,那是因为通常情况下,框架具有“通用性”(没有通用性的也就不叫框架了),也就是说,框架可以使用在多个场合下,而框架编写者则完全不知道每个具体场合是什么样的,有哪些功能,每个功能怎么实现的,既然不知道具体情况,那么框架编写者只有使用一系列抽象引用临时代替了。

现在既然不确定性无可避免,那么,怎么才能让框架本身与客户端代码(框架使用者编写的代码)能够很好的“协同工作”呢?此时,我们打开我们发达的大脑,开始拼命想象,喷血联想,协同工作?好像通信中经常听到的词语,两个远程主机如果想要协同工作,双方必须遵守同一个通信协议,如下图:

图5

那么,我们完全可以把“框架”当做服务端,框架使用者编写的代码就为客户端了,他们之间协同工作也应该遵守相同的协议,如下图:

图6

具体编码中,这个协议就表现为接口(Interface)或基类(相对而言)这样的东西,框架中使用这些东西访问客户端代码,客户端代码也必须实现这些接口或者派生自这些基类。

像框架这种依赖于抽象的做法在解决通用性的同时,还能最大限度降低耦合度,框架编写者完全不用关心使用者的具体实现,使用者只要遵守协议,怎么实现不归框架管。           当然也有缺陷,就是框架只能通过事先规定的协议去访问客户端代码,客户端代码中如果有协议之外的东西,框架是访问不到的。这就要求框架编写者在编写框架的时候考虑充分,将所有有可能涉及到的东西都归纳到协议之中。

本篇需结合前面三篇博客(与“泵”有关的)一起阅读。希望对各位有帮助。

.net开发笔记(十六) 对前部分文章的一些补充和总结的更多相关文章

  1. OpenCV开发笔记(六十四):红胖子8分钟带你深入了解SURF特征点(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  2. OpenCV开发笔记(六十五):红胖子8分钟带你深入了解ORB特征点(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  3. OpenCV开发笔记(六十九):红胖子8分钟带你使用传统方法识别已知物体(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  4. S3C2416裸机开发系列十六_sd卡驱动实现

    S3C2416裸机开发系列十六 sd卡驱动实现 象棋小子    1048272975 SD卡(Secure Digital Memory Card)具有体积小.容量大.传输数据快.可插拔.安全性好等长 ...

  5. python3.4学习笔记(十六) windows下面安装easy_install和pip教程

    python3.4学习笔记(十六) windows下面安装easy_install和pip教程 easy_install和pip都是用来下载安装Python一个公共资源库PyPI的相关资源包的 首先安 ...

  6. Java开发笔记(六十)匿名内部类的优势

    前面依次介绍了简单接口和扩展接口,给出的范例都是自定义的接口代码,其实Java系统本身就自带了若干行为接口,为了更好地理解系统接口的详细用法,接下来还是从一个基础的例子出发,抽丝剥茧地逐步说明接口的几 ...

  7. Java开发笔记(六十二)如何定义函数式接口

    前面介绍了Lambda表达式的用法,从实践中发现它确实极大地方便了开发者,然而不管是匿名内部类还是Lambda表达式,所举的例子都离不开各类数组的排序方法,倘使Lambda表达式仅能用于sort方法, ...

  8. Java开发笔记(六十四)静态方法引用和实例方法引用

    前面介绍了方法引用的概念及其业务场景,虽然在所列举的案例之中方法引用确实好用,但是显而易见这些案例的适用场合非常狭窄,因为被引用的方法必须属于外层匿名方法(即Lambda表达式)的数据类型,像isEm ...

  9. Java开发笔记(六十五)集合:HashSet和TreeSet

    对于相同类型的一组数据,虽然Java已经提供了数组加以表达,但是数组的结构实在太简单了,第一它无法直接添加新元素,第二它只能按照线性排列,故而数组用于基本的操作倒还凑合,若要用于复杂的处理就无法胜任了 ...

  10. Java开发笔记(六十六)映射:HashMap和TreeMap

    前面介绍了两种集合的用法,它们的共性为每个元素都是唯一的,区别在于一个无序一个有序.虽说往集合里面保存数据还算容易,但要从集合中取出数据就没那么方便了,因为集合居然不提供get方法,没有get方法怎么 ...

随机推荐

  1. Spring Boot项目的打包和部署

    补充一点:搜索了下别人Spring Boot部署方案,大多都说:①packaging设为war:②要添加Spring Boot的tomcat依赖:③修改output路径,但是使用STS新建Spring ...

  2. ORACLE RAC 11G 更改 /etc/hosts文件

    来自官方文档:()Can I change the public hostname in my Oracle Database 10g Cluster using Oracle Clusterware ...

  3. js处理匿名函数

    首先js 有DOM0 和DOM2级事件 DOM 0级事件处理一般是直接把一个函数分配给一个事件处理程序,既可以在元素中直接分配一个事件处理程序 一个元素可以绑定多个事件 DOM0: <div i ...

  4. 服务器内存UDIMM与RDIMM区别

    UDIMM 全称是无缓冲双信道内存模块(Unbuffered Dual In-Lne Memory Modules),它不支持服务器内存满配,就是最高容量了,因为使用UDIMM内存时最大使用每通道只能 ...

  5. phantomjs和angular-seo-server实现angular单页面seo

    1.下载phantomjs,并配置环境变量为   eg:E:\phantomjs-2.1.1-windows\bin 2.下载angular-seo-server 3.windows下:cmd eg: ...

  6. CentOS下搭建SVN

    1.安装svn yum -y install subversion 2.创建hydata目录 mkdir -p /var/svn/hydata 3.创建 m 目录 mkdir -p /var/svn/ ...

  7. RPLIDAR使用和测试

    采购的RPLIDAR刚刚到货,先拆封,内部包括雷达主机.线缆.串口USB转接.USB线.   根据说明在此处下载驱动和SDK,雷达的数据实际是通过串口输出的,所以驱动只是针对串口USB转换芯片CP21 ...

  8. mysql 乱码问题(程序界面显示正常,mysql command line显示乱码)

    今天用java写一个程序,用的是mysql数据库.界面出现乱码,然后写了一个过滤器结果了乱码问题. 但是,当我在mysql command line 中查询数据的时候,在界面上显示正常的数据,在mys ...

  9. Mysql连接到Visual studio注意

    测试环境:mysql(mysql-connector-net-6.9.7.msi)+visual studio2012 1.mysql安装ok的情况下!在控制面板管理工具-数据源(ODBC)--用户D ...

  10. ubuntu14.04设置terminal配色方案以配合使用vim的Solarized插件

    在安装vim插件之前,首先安装Vundle插件,用来管理vim插件,安装方法查看Vundle在github上的指南.在安装vundle的时候出现了一个错误E117:unknown function v ...