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

简介:

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

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

一、回调方法。

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

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

  View Code

你可能会说,func()是我主动调用的,所以它不是回调方法,但是你要明白,不管你代码怎么写,它最终执行的主动权不是在你手中,既然主动权不在你手中,那么它就应该属于回调。

2)这里的“系统”其实是相对而言的,我前面都把它当做操作系统,其实不然,我们口口声声说“系统调用的方法属于回调方法”,这里的“系统”绝大多数是指编程中使用到的“框架”,你比如如下代码:

  View Code

这里的“回调方法”btn1_Click是由“微软Winform开发框架”调用的,因此“系统”就是指“Winform开发框架”。1)中讲到的“任何一个程序开始运行,都是由操作系统调用某一个入口方法”,那么这里的“系统”就是指操作系统。任何都是相对而言的,任何“框架”对于操作系统而言,都可以算得上是“回调”,而我们写的所有代码相对于“框架”而言,也都能算得上“回调”。

3)我们写的程序,就用Windows Form应用程序为例子吧,对于操作系统而言,第一个回调方法应该是Main方法,由Main进入Application.Run(…),至于何时结束,当然是Main方法最后的一个“反花括号”。至于中间使用了哪些“框架”,我们自己又写了哪些代码,对于操作系统而言,全部都是一样的,那都属于“回调代码”。总之,广义上讲,没有不是“回调”的代码(方法)。

图1

二、泵。

这是个非常重要的概念,也是本篇文章之后的核心。 生活中提到泵,我们至少可以想到两点:

1)持续运作。也就是说,泵能长时间循环工作。

2)传输作用。泵能够将水等液体从一个地方运输到另外一个地方,供其他人使用。

图2

以上是生活中见到的泵,那么在编程中,泵是指什么呢?类比一下,其实很容易想到,程序中的泵具备以下两个特点:

1)  循环执行。类似一个while循环,能够长时间循环工作。

2)  数据传输。它能够将数据(不再是水等液体)从一个地方搬迁到另一个地方,供其他人使用。

程序中的泵,最简单的利用while就能实现,如下代码:

  View Code

    如你所见,以上代码很好的解释了编程中的“泵”含义。那么,程序中为啥需要使用泵?原因可以从泵的作用中找,我们很清楚,平时调试一些代码时,可能很多人使用Console程序,如果我们在代码中不设置一个阻塞断点的话(如Console.Read()),程序执行完毕后,黑屏就会消失,我们看不到任何结果,如下代码:

  View Code

    其根本原因,是我们写的程序是“线段”状,所谓线段,即它是有限长度直线,线程从main开始,笔直的结束了。可是我们用的大多数软件从来不会一开始运行,马上就结束(除非某些特定功能软件)了,他们绝大多数都是长时间持续运行,好了,听到“长时间持续运行”,我们就想到了“泵”有这种功能,是的,“泵”不仅仅有这种功能,它还能将数据从一个地方搬迁到另外一个地方,供其他人使用。到此,程序中使用“泵”是必然。

    讲到这里,相信有很多人开始意识到自己在编程中已经见过或者使用过“泵”,比如一般界面编程中的“windows消息循环”,如果有人说没听说,它见过你你却没见过它,那说明你对Windows桌面开发还不是很了解,建议看看本系列博客之透过现象看本质。反观Windows操作系统,它其实就是一个非常大的“泵”,长时间持续工作,从“串口”、“键盘鼠标”、“麦克风”、“摄像头”、“网络端口”等等缓冲区中获取数据,传递给各种各样的程序使用。看一张程序中“泵”结构图:

图3

实际编程中,用到“泵”的地方很多,只要某一个环节(跟模块的意思差不多,只是个人觉得环节更具体,模块指的范围太大,下同)需要长时间持续工作,同时不断存在一系列数据需要被处理,那么就可以使用泵。总结一下,程序中需要使用“泵”的地方有两个明显特点:

1)  该环节需要持续运作,也就是需要循环运行,不会马上结束;

2)  有一些数据需要被处理,这些数据一般存放在某个容器中,需要不断地取出来传给别人使用。

    前面提到的“Windows消息循环”就是一个泵,它符合以上两个特点,第一,UI线程不可能马上结束,需要长时间持续运作;第二,源源不断的有Windows消息(一种数据)需要被处理(数据存放在线程的消息队列中)。为了更好理解,附图一张:

图4

既然“泵”是一种循环,并且每一次循环执行都是需要时间损耗的,这样就出现了一个问题,如果某一次循环耗时太长,单次循环不能立刻返回,那么需要处理的数据就会大量累积,不能及时取出处理,造成堵塞。这个问题其实我们经常遇见过(或许又是它天天见到你,你却没看见它),我们编程时,有时候会遇见界面卡、不流畅、反应慢等现象,大部分原因就是因为,消息处理泵(消息循环)某一次循环耗时太长,循环不能迅速返回,windows消息大量累积,得不到及时处理,造成界面反应迟钝。

三、线程和方法的关系

这个问题其实本系列第一篇博客中讲到过,线程和方法没有一对一的关系,一个线程可以调用许多方法,一个方法也可以运行在多个线程中。前面一句很好理解,后面一句其实也好理解,看如下代码:

  View Code

如果func中没有访问外部变量,基本上不会出问题,但是如果func中访问了外部对象,而该对象不是线程安全的,那么你就得在func中做一些“安全措施”了,这点很容易被忽略,如下:

  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中取出消息,分配给对应的窗口过程,供其使用。

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

  1. 各所其职,符合软件开发的原则
  2. 如果什么东西都放在一个“泵”中做,必然会影响原有的效率,比如将“数据分析”放在“数据采集泵”中,势必会影响采集的效率,其他类似。

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

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

图6

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

  1. 前者鼠标输入,后者远程输入;
  2. 前者有分析泵,后者照样有;
  3. 前者激发一些事件,比如Winform中的Click事件,你可以在事件处理程序中访问数据库、操作IO、更新界面,后者你注册相关事件之后,照样可以做这些事情。

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

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

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

作者:周见智 
出处:http://www.cnblogs.com/xiaozhi_5638/ 
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

标签:  c#Winform

本文转自周见智博客博客园博客,原文链接:http://www.cnblogs.com/xiaozhi_5638/p/3167794.html,如需转载请自行联系原作者
目录
相关文章
|
11天前
|
人工智能 量子技术 C#
【专栏】.NET 开发:开启数字化新时代
【4月更文挑战第29天】.NET开发在数字化新时代中发挥关键作用,借助跨平台能力、高性能和现代编程语言支持,如C#,助力企业实现数字化转型。通过企业级应用开发、移动应用和云计算集成,.NET加速业务流程和提升用户体验。未来,.NET将涉足AI、ML、MR/AR及量子计算,持续推动技术创新和数字化转型。开发者应提升技能,适应高性能需求,把握发展机遇。
|
11天前
|
缓存 监控 算法
【专栏】.NET 开发:实现卓越性能的途径
【4月更文挑战第29天】本文探讨了.NET开发中的性能优化,强调了理解性能问题根源和使用分析工具的重要性。基础优化包括代码优化(如减少计算、避免内存泄漏)、资源管理及选择合适算法。高级策略涉及并行编程、缓存策略、预编译(AOT)和微服务架构。持续性能测试与监控是关键,包括性能测试、监控分析和建立优化反馈循环。开发者应持续学习和实践性能优化,以构建高性能应用。
|
11天前
|
开发框架 .NET C#
【专栏】理解.NET 技术,提升开发水平
【4月更文挑战第29天】本文介绍了.NET技术的核心概念和应用,包括其跨平台能力、性能优化、现代编程语言支持及Web开发等特性。文章强调了深入学习.NET技术、关注社区动态、实践经验及学习现代编程理念对提升开发水平的重要性。通过这些,开发者能更好地利用.NET构建高效、可维护的多平台应用。
|
11天前
|
机器学习/深度学习 vr&ar 开发者
【专栏】.NET 技术:引领开发新方向
【4月更文挑战第29天】本文探讨了.NET技术如何引领软件开发新方向,主要体现在三方面:1) 作为跨平台开发的先锋,.NET Core支持多操作系统和移动设备,借助.NET MAUI创建统一UI,适应物联网需求;2) 提升性能和开发者生产力,采用先进技术和优化策略,同时更新C#语言特性,提高代码效率和可维护性;3) 支持现代化应用架构,包括微服务、容器化,集成Kubernetes和ASP.NET Core,保障安全性。此外,.NET还不断探索AI、ML和AR/VR技术,为软件开发带来更多创新可能。
|
11天前
|
物联网 vr&ar 开发者
【专栏】.NET 技术:为开发注入活力
【4月更文挑战第29天】本文探讨了.NET技术的创新,主要体现在三个方面:1) .NET Core实现跨平台开发革命,支持多种操作系统和硬件,如.NET MAUI用于多平台UI;2) 性能提升与生产力飞跃,C#新特性简化编程,JIT和AOT优化提升性能,Roslyn提供代码分析工具;3) 引领现代化应用架构,支持微服务、容器化,内置安全机制。未来,.NET 7将带来更多新特性和前沿技术整合,如量子计算、AI,持续推动软件开发创新。开发者掌握.NET技术将赢得竞争优势。
|
11天前
|
人工智能 前端开发 Cloud Native
【专栏】洞察.NET 技术的开发趋势
【4月更文挑战第29天】本文探讨了.NET技术的三大发展趋势:1) 跨平台与云原生技术融合,通过.NET Core支持轻量级、高性能应用,适应云计算和微服务;2) 人工智能与机器学习的集成,如ML.NET框架,使开发者能用C#构建AI模型;3) 引入现代化前端开发技术,如Blazor,实现前后端一致性。随着.NET 8等新版本的发布,期待更多创新技术如量子计算、AR/VR的融合,.NET将持续推动软件开发的创新与进步。
|
11天前
|
开发框架 物联网 测试技术
【专栏】.NET 开发:打造领先应用的基石
【4月更文挑战第29天】本文探讨了.NET开发框架为何成为构建领先应用的首选。高性能与稳定性是.NET的核心优势,它采用先进的技术和优化策略,如.NET Core的轻量级设计和JIT/AOT编译模式。跨平台兼容性让开发者能用相同代码库在不同操作系统上构建应用。现代化的开发体验,如C#语言的创新特性和Visual Studio的强大工具,提升了开发者生产力。丰富的生态系统和广泛支持,包括庞大的开发者社区和微软的持续投入,为.NET提供了坚实后盾。
|
4月前
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
46 0
|
15天前
|
开发框架 前端开发 JavaScript
JavaScript云LIS系统源码ASP.NET CORE 3.1 MVC + SQLserver + Redis医院实验室信息系统源码 医院云LIS系统源码
实验室信息系统(Laboratory Information System,缩写LIS)是一类用来处理实验室过程信息的软件,云LIS系统围绕临床,云LIS系统将与云HIS系统建立起高度的业务整合,以体现“以病人为中心”的设计理念,优化就诊流程,方便患者就医。
21 0
|
2月前
|
开发框架 前端开发 .NET
进入ASP .net mvc的世界
进入ASP .net mvc的世界
32 0