补充有两个:

  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. ReferenceQueue<T>随笔

    参考资料: ReferenceQueue食用手册 java引用食用手册 ReferenceQueue源代码里面很好的展示了java队列的实现思路, 以及多线程观察者的实现思路 多线程观察者实现思路: ...

  2. tkinter 在 x window 下的字体设置格式

    X Font Descriptors # X Font Descriptors are strings having the following format (the asterisks repre ...

  3. C++中关于文件的读写

    在C++的学习过程中,我们时常要用到对文件的操作,下面我们讲一下文件的读写. 首先,读.也就是把已有的文件读到控制台上,那么如何操作呢?首先要将文件操作的输入输出流包含进去. <fstream& ...

  4. windows读取mac格式移动硬盘的方法

    本文记录了一些window与mac数据在移动设备上互相拷贝的经验. 一.准备 家里有一台mac电脑,限于硬盘空间比较小,需要定期备份一些数据. 由于备份数据大小在20G左右,并且并没有压缩为一个压缩文 ...

  5. Numpy 学习之路(1)——数组的创建

    数组是Numpy操作的主要对象,也是python数据分析的主要对象,本系列文章是本人在学习Numpy中的笔记. 文章中以下都基于以下方式的numpy导入: import numpy as np fro ...

  6. Microsoft.Office.Interop.Word.Document.Open returns null on Windows Server 2008 R2

    系统终于通过UAT,可以上线了.一遍测下来还行,可是为什么word转PDF就是不行呢?查了一下log,原来在wordApp.Documents.Open来打开生产的word文件的时候,返回一直是空.之 ...

  7. yii安装 /You don't have permission to access on this server

    在安装yii的时候 ,当打开了init.bat进行配置的时候小黑本弹出了个小黑框立刻就关闭了,  进入cmd模式再打开init.bat就出现了"You don't have permissi ...

  8. How to copy remote computer files quickly to local computer

    if we want copy file from VM(Remote VM) to local computer. Always can not easy copy file so easy. no ...

  9. CodeForces 742B Arpa’s obvious problem and Mehrdad’s terrible solution (暴力枚举)

    题意:求定 n 个数,求有多少对数满足,ai^bi = x. 析:暴力枚举就行,n的复杂度. 代码如下: #pragma comment(linker, "/STACK:1024000000 ...

  10. 一台MySQL服务器启动多个端口

    一台MySQL服务器启动多个端口 在测试Mysql多主一从服务器,即一个从服务器多端口同步不同主库.本文记录了开启不同端口的操作. 详细步骤: 1.首先要先把my.cnf配置文件复制一份,开几个端口要 ...