您的位置:首页 > 其它

.net开发笔记(十三) Winform常用开发模式第一篇

2013-07-02 19:53 260 查看
上一篇博客最后我提到“异步编程模型”(APM),之后本来打算整理一下这方面的材料然后总结一下写篇文章与诸位分享,后来在整理的过程中不断的延伸不断地扩展,发现完全偏离了“异步编程”这个概念,前前后后所有的加起来完全可以写一篇关于框架原理的东西,而“异步编程”只是其中的一小部分,后来我一狠心,打算把所有的都包含进来写出来,希望给诸位带来帮助。

文章开始之前,先了解几个概念:

一、回调方法。

这个概念想必都很清楚,被系统调用的方法就叫做“回调方法”。是的,描述的没错,通常我们注册一个事件,事件处理程序就属于“回调方法”。可是不知道诸位有没有想过,我们在编程过程中,哪些不属于“回调方法”呢?有人肯定会说,我们主动调用的方法就不属于回调方法。先不管对不对,我们先来考虑一些问题:系统调用方法A,那么方法A就是回调方法,如果我在A中又调用了方法B,那么B算作回调方法吗?再者,什么是所谓的“系统”?指操作系统吗?亦或是我们编程中使用到的“框架”?最后,我们写的程序,系统从哪开始调用?又是在哪结束?接下来我一一做解答:

1)广义上讲,我们写的每一行代码都属于“回调代码”(由代码组成的方法就叫回调方法,意思差不多),为什么这么讲?因为我们都知道,任何一个程序开始运行,都是由操作系统调用某一个入口方法,那么显然,这个入口方法就是理所当然的“回调方法”,进入“入口方法”中去之后,就会执行许许多多的其他代码,也就是说,不管你来回调用了多少次、嵌套调用了多少次,我们编写的所有代码都间接被操作系统调用。那么像上面有人可能提到的“主动调用的方法不属于回调方法”,其实在程序中,压根儿就没有你能主动调用的方法,如果你写如下的代码:

List<int> list = new List<int>();
void func()
{
list.Add(DateTime.Now.Hours);
}


View Code
我们在设计func方法的时候,应该考虑该方法将来可能在哪些地方被调用,如果只在一个线程中调用(比如UI线程),那么没有任何问题,但是如果func有可能运行在多个线程中,那么你就需要做一些“安全措施”了,比如加锁等。

总之你在设计一个方法的时候,务必要考虑这个方法将来可能在哪些地方调用,如果是控件类的成员方法,你更要考虑,因为控件类成员方法一般都会方法UI,如果这个成员方法将来被其它线程(非UI线程)调用,那么就会出现异常。

以上三个概念有些本篇文章有用,有些阅读下一篇我分享一个UDP通信demo的时候有用。

正文:

理解以上三个概念,我认为对熟悉接下来要说的有很大帮助。下面,我介绍一个winform中常用到的开发模式,该模式就是通过“泵”来实现的,不敢说诸位平时用到的所有的框架都是基于这种模式,但我敢说我用到过的框架都是以此为基础的(下一篇博客,我会分享一个UDP通信demo,用具体的实例来说明该开发模式)。

据我开发经验,总结出来4种需要使用到“泵”的场合:

(1)当然是之前提到过的有关“Windows消息循环”这一块,它几乎是所有Windows桌面应用程序开发的精髓。

(2)Socket通信这一块,包括UDP和TCP两部分,我之后会做一个UDP的Demo。

(3)串口通信这一块。

(4)麦克风、摄像头数据采集这一块。

大概常用的有这四种,其实意思都差不多,就是之前我们讲到的:都涉及到持续运行,都需要不断的取数据、分配(传递)数据、别人再处理(使用)数据。我具体说一说(1)和(2),弄清楚前两个,后面两个也就清楚明了了。

(1)要了解“Windows消息循环”,我们先得了解一个流程:鼠标点击按钮,鼠标驱动采集物理信息,转换成数字信息,存在一个缓冲区A,我们称该数字信息为“原始数据”(你可以理解为包含鼠标XY坐标、左右键状态等等),之所以称之为“原始数据”,是因为该数据跟咱们的程序没有任何关联,它只是简单地包含了鼠标当前状态信息。接下来就有一个“数据采集泵”循环将这些原始数据采集过来,放到另外一个缓冲区B,对应有一个“数据分析泵”,循环将缓冲区B中的原始数据取出,分析该“原始数据”,参照Windows系统“内部数据库”(一种存放窗体、线程等资源的组织),将原始数据转换成标准的“Windows消息”(一种数据结构,包含窗体Handle,类型、参数等),接着再将转换之后生成的“Windows消息”存放到缓冲区C(就是我们经常听到的消息队列),此时,又有一个“数据处理泵”(就是我们常说的消息循环)循环取出缓冲区C中的“Windows消息”,分配该消息给对应的窗口过程(WndProc),供其使用(处理),窗口过程就会激发Click事件,接着,你的事件处理程序(如btn1_Click)就会被调用,至此,整个过程结束。上图一张,更清楚:



图5

如我们所见,整个过程使用了3个泵,他们互相配合使用,“数据采集泵”负责将“原始数据”从缓冲区A传递到缓冲区B,“数据分析泵”负责取出缓冲区B中的原始数据,然后进行分析,转换成Windows消息(一种程序能够识别的数据结构),进而传递到缓冲区C,也就是我们常说到的“消息队列”,然后“数据处理泵”,我们常说的“消息循环”,循环从缓冲区C中取出消息,分配给对应的窗口过程,供其使用。

有人可能会说,干嘛要分三个“泵”,一个“泵”不就能搞定吗,在“数据采集泵”中分析数据、转换数据、处理数据?不能的原因至少有两个:

各所其职,符合软件开发的原则

如果什么东西都放在一个“泵”中做,必然会影响原有的效率,比如将“数据分析”放在“数据采集泵”中,势必会影响采集的效率,其他类似。

以上是“泵”在Windows消息处理中的应用。接下来说一下Socket编程中的应用,我以UDP通信为例,TCP类似。

(2)我们先理清UDP通信流程:远程主机给本地主机发送一个UDP数据包,需要注意的是,在到达本地主机之前(传输过程中),数据包应该是一种物理信息,经过网卡驱动转换后,物理信息变成数字信息,存放在缓冲区A中(一串字节流,称之为原始数据),此时,需要一个“数据接收泵”循环取出缓冲区A中的原始数据(UDP中该数据应该是一个完整的数据包),将其存放到缓冲区B中,对应有一个“数据分析泵”循环取出缓冲区B中的原始数据,根据事先规定好的“协议”(一种通信规则,通信各方必须同时遵守),将该原始数据解析成程序可识别数据(数据头,程序中可识别数据,远程IP端口等),紧接着将解析之后的数据存放到缓冲区C,对应又有一个“数据处理泵”循环从C中取出数据,分配数据,通知他人处理。上图一张:



图6

现在已经很清楚,这个模式跟“windows消息循环”是一个意思,接收数据->分析数据->处理数据,每个环节都有一个“泵”与之关联,当然还有一个缓冲区。其实再拓展一下,我们会发现它们都有输入,都有分析,都有响应

前者鼠标输入,后者远程输入;

前者有分析泵,后者照样有;

前者激发一些事件,比如Winform中的Click事件,你可以在事件处理程序中访问数据库、操作IO、更新界面,后者你注册相关事件之后,照样可以做这些事情。

再不说了,说多了都是泪,发现原来它们都是一样一样的。TCP跟UDP差不多,只是服务端需要有“socket侦听泵”用来监听socket连入,而且每个连入的socket都对应有自己的“数据接收泵”跟“数据分析泵”,原因很简单,因为TCP按照“流”来传输数据的,数据包之间没有界限,某一次接收到的“原始数据”可能不是一个完整的包,因此,每个客户端socket必须有自己的“数据接收泵”和“数据分析泵”以及对应的缓冲区,并且“数据分析泵”中还要具备检测完整包的功能。TCP版本Demo以后我再做一个,稍微比UDP复杂一点。

以上是所有的介绍,理论性的东西非常多,下一篇文章我打算分享一个UDP通信demo,采用本篇所讲内容,简单的实现了类似飞鸽传书的功能。

顺便带个题外话,这一系列文章可能跟实际具体开发关联性不是很大,特别像之前说到的“运行时和设计时”、“winform框架原理”等等这些,基本上跟平时工作沾不上边,我也没有刻意去写平时工作中遇到的问题,写出来的东西大都是概念性、原理性偏多一些。各位在看的时候没必要跟实际工作内容做比较,全当做是一种业余研究就OK了, O(∩_∩)O~。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: