当前位置:  编程技术>.net/c#/asp.net

.Net Winform开发笔记(四)透过现象看本质

    来源: 互联网  发布时间:2014-10-16

    本文导语:  写在前面: 从一个窗体的创建显示,再到与用户的交互,最后窗体关闭,这中间经历过了一系列复杂的过程,本文将从Winform应用程序中的Program.cs文件的第一行代码开始,逐步分析一个Winform应用程序到底是怎样从出生走向死亡...

写在前面:
从一个窗体的创建显示,再到与用户的交互,最后窗体关闭,这中间经历过了一系列复杂的过程,本文将从Winform应用程序中的Program.cs文件的第一行代码开始,逐步分析一个Winform应用程序到底是怎样从出生走向死亡,这其中包括Form.Show()和Form.ShowDialog()的区别、模式对话框形成的本质原因、消息循环、Windows事件与.net中事件(Event)的区别、System.Windows.Form.Application类的作用、以及我之前一篇博客中(.Net开发笔记(二)网址)面试题中的最后一题,从Windows消息层次讲述点击按钮弹出一个MessageBox的详细过程。

我承认,不了解以上问题的Coder可能也能写出非常出色非常复杂的Winform应用程序出来,但不是有句老话么,知其然,亦要知其所以然。

另外,看本篇博客(或者接下来几篇)必须了解Win32编程知识,如果不清楚的同学,可以先上网学习学习,这就像学习MFC最好也得懂点Win32编程,本文不解释什么是Win32 API、什么是句柄、更不会解释什么是回调方法。

一个引子:
一个线程,具体啥定义我也就不说了,太抽象,我觉得还是把它看做是一个方法(函数),当然包括方法体中调用的其它方法,线程有开始,也有结束,分别可以比作方法的开始和结束,我们不管一个方法体内调用了多少其它方法,只要程序没写错,这个方法肯定有返回的时候,也就是说,在正常情况下,一个线程开始后,肯定会有退出(结束)的时候,那么,如果想让一个线程不会太快结束,我们可以在方法体内写些啥?“阻塞方法!”有人可能马上说,因为阻塞方法一般不会马上返回,只有等它执行完毕后,才会返回,在它返回前,调用它的方法不会继续运行下去,的确,在我学习C++语言的时候,经常写Console程序(那时候也只会写这玩意儿),为了不让黑屏闪一下就消失了,看不到运行结果,我经常在程序最后加上一行“int a;cin>>a;”,我当时也不知道为啥要这样写,只知道这样写了,程序不会马上结束。其实后来才知道,那行代码就是阻塞了整个程序,当你输入一个整数,按下回车,程序就会结束。

“阻塞方法”确实是一种方法,但是如果我们想在线程执行过程中,与外部(用户)进行交互,也就是说,在线程执行期间,用户可以通过输入来控制线程的运行情况,同样在Console程序中,该怎么实现?现在问题来了,不紧不能让线程马上结束,还要与用户有所交互,而且不应该只交互一次(否则,上面提到的cin>>a;完全够用),该怎么搞?不止交互一次?那么很容易就能想到“循环”,用循环来使线程与用户进行交互再好不过了,为了与本文相联系,用C#代码编写如下:
代码如下:

View Code
void main()
{
string input = “quit”;
while((input=Console.ReadLine())!=”quit”)
{
Console.WriteLine(“input string :” + input );
}
Console.WriteLine(“thread exit”);
Console.ReadKey();
}

非常简单的一段代码,程序运行后,有了while循环,不会马上结束,它会不停的等待用户输入,然后输出用户输入的字符串(模拟响应用户操作),直到用户输入“quit”后,循环才结束。这段利用while循环和Console.ReadLine()写出来的程序虽然短小简单,却是后面我们要谈到的Winform应用程序(其实所有的Windows应用程序都一样,无论是MFC还是Delphi或者其他搞出来的桌面程序)的精髓。当然,这段代码确实太简陋了,所以我才说它是精髓,O(∩_∩)O~。既然太简陋,那我们再改改吧。要改就改复杂一点。
初加工:
代码如下:

View Code
///矩形类
Class Rect
{
int _id; //矩形唯一标示
string _text; //矩形中心显示的文本
Size _size; //矩形大小
Point _location; //矩形的位置
Bool _alive; //是否存活
Public int ID
{
Get
{
Return _id
}
Set
{
_id = value;
}
}
Public string Text
{
Get
{
Return _text;
}
Set
{
_text = value;
}
}
Public Size Size
{
Get
{
Return _size;
}
Set
{
_size = value;
}
}
Public Point Location
{
Get
{
Return _location;
}
Set
{
_location = value;
}
}
Public bool Alive
{
Get
{
Return _alive;
}
Set
{
_alive = value;
}
}
Public Rect(int id,string text,Size size,Point location)
{
_id = id;
_text = text;
_size = size;
_location = location;
_alive = true;
Console.WriteLine(“[” + ID.ToString() + “] 号矩形创建成功!”);
}
//矩形对外唯一接口,对矩形的所有操作必须调用此方法,下称“矩形过程”
Public void RectProc(int id,int type,object leftParam,object rightParam)
{
Switch(type) //这个type就是后面说的“信号类型”, 应该跟Sgl枚举一一对应
{
Case 1: //移动、改变大小
{
Size newSize = (Size)leftParam;
Position newLocation = (Point)rightParam;
This.Size = newSize;
This.Location = newLocation;
Console.WriteLine(“[” + ID.ToString() + “] 号矩形改变位置:大小为(” + this.Size.Width+”,” + this.Size.Height + “),位置为(”+this.Location.Left + “,” + this.Location.Top + “)” );
Break;
}
Case 2: //显示信息
{
Console.WriteLine(“[” + ID.ToString() + “] 号矩形显示信息:大小为(” + this.Size.Width+”,” + this.Size.Height + “),位置为(”+this.Location.Left + “,” + this.Location.Top + “),Text为 ” + this.Text );
Break;
}
Case 3: //关闭
{
Console.WriteLine(“[” + ID.ToString() + “] 号矩形关闭”);
Alive = false;
Break;
}
//……
Default:
{
//默认处理
}
}
}
}
//信号类,表示一种信号,包含信号接收者ID,信号类型Type,信号两个参数LeftParam、RightParam
Class Signal
{
Int _id; //接受者id
Int _type; //信号类型
Object _leftParam; //参数1
Object _rightParam; //参数2
Public int ID
{
Get
{
Return _id;
}
Set
{
_id = value;
}
}
Public int Type
{
Get
{
Return _type;
}
Set
{
_type = value;
}
}
Public object LeftParam
{
Get
{
Return _leftParam;
}
Set
{
_leftParam = value;
}
}
Public object RightParam
{
Get
{
Return _rightParam;
}
Set
{
_rightParam = value;
}
}
Public Signal(int id,int type,object leftParam,object rightParam)
{
_id = id;
_type = type;
_leftParam = leftParam;
_rightParam = rightParam;
}
}
// 信号类型枚举 RS即为RectSignal
Enum Sgl
{
RS_POSITIONCHANGE = 1, //移动矩形,大小变化
RS_SHOWINFO = 2, //矩形显示自己信息
RS_KILL = 3 //关闭矩形
//……很多省略
}
/* 信号格式(不同的Sgl,Signal对象内容完整度不一样)
* RS_POSITIONCHANGE: ID必须,Type必须,LeftParam必须,RightParam必须
* RS_SHOWINFO ID必须,Type必须
* RS_KILL: ID必须,Type必须
* ……很多省略
*/
/// 主线程
/// 测试代码
Static class ZZThread
{
Static List allRects = new List(); //整个线程运行过程中,存在的Rect对象
//线程入口
Public Static void Main()
{
//初始化4个Rect对象,添加到集合中
allRects.Add(new Rect(1,”my name is Rect1”,new Size(100,100),new Point(10,10)));
all.Rects.Add(new Rect(2,”my name is Rect2”,new Size(455,250),new Point(100,150));
allRects.Add(new Rect(3,”my name is Rect3”,new Size(300,500),new Point(250,100));
allRects,Add(new Rect(4,”my name is Rect4”,new Size(300,600),new Point(50,80));
//开始循环接收用户输入,作出反应
Signal signal = null;
While(GetSignal(out signal)) //接收信号
{
DispatchSignal(signal); //分配信号到各个Rect
}
Console.WriteLine(“The Thread Exit”);
// Console.ReadKey(); //阻塞查看运行情况
}
Static bool GetSignal(out Signal signal)
{
START:
String input = Console.ReadLine(); //接受用户输入
String[] inputs = input.Split(“ ”);
If(inputs.Length == 1) //用户输入QUIT,退出
{
If(inputs[0] == “QUIT”)
{
Return false;
}
Else
{
Console.WriteLine(“参数格式错误!”);
Goto START;
}
}
// 必须提供Rect的id、以及信号类型,参数可选
// 没做格式验证,所有必须输入整形数据
If(inputs.Length == 2) //只提供了Rect的id和信号类型
{
signal = new Signal(int.parse(intputs[0]),int.Parse(inputs[1]),null,null);
return true;
}
If(inputs.Length == 4) //只提供了Rect的id、信号类型以及第一个参数
{
signal = new Signal(int.Parse(inputs[0]),int.Parse(intputs[1]),new Size(int.Parse(inputs[2]),int.Parse(inputs[3])),null);
return true;
}
If(inputs.Length == 6) //四个参数全部提供
{
signal = new Signal(int.Parse(inputs[0]),int.Parse(inputs[1]),new Size(int.Parse(inputs[2]),int.Parse(inputs[3])),new Point(int.Parse(inputs[4]),int.Parse(inputs[5])));
return true;
}
Console.WriteLine(“参数格式错误!”);
Goto START;
}
Static void DispatchSignal(Signal signal)
{
Foreach(Rect rect in allRects)
{
If(rect.ID == signal.ID && rect.Alive)
{
rect.RectProc(signal.ID,signal.Type,signal.LeftParam,signal.RightParam);
break;
}
}
}
}

解释一下,代码虽然多了一点,可大概结构还是没变(其实我们见到的其他所有框架,结构虽然复杂得很,可其精髓的代码也就不到一半,其余的都是在精髓代码上扩充来的,增加各种各样的功能),如你所见,跟之前的意思一样,线程中有一个While循环、接收用户输入、响应用户输入(操作)。不一样的是,将接受用户输入部分封装到一个GetSignal方法中去了,将响应用户输入部分封装到一个DispatchSignal方法中去了,为了更好的反应用户操作可以“多样化”(不再是以前输入一个字符串,线程再将源字符串输出),我定义了一个Rect类,该类表示一个矩形,可以供用户操作,我还定义了一个Signal类,该类表示一个信号,用户的所有输入都可以看做是一个信号,信号中包括信号接受者(ID)、信号类型、以及信号可能附带的参数,此外,(不要嫌麻烦O(∩_∩)O~)我还定义了一个信号类型枚举,用来表示用户操作的类型。
现在,我们来理清一下整个线程运行的流程:
1.ZZThread中的静态方法Main开始运行,线程开始
2.新建四个Rect对象,将其加到一个集合中,供用户操作
3.开始一个while循环,GetSignal接受用户输入,输入格式需按照规定格式
4.GetSignal方法返回,如果用户输入不是“QUIT”字符串,返回true,否则返回false,while循环结束,线程退出。
5.用户输入不是“QUIT”,GetSignal方法的signal参数即为用户输入的信息(该信号应该包括用户想要操作的对象、操作的类型、以及一些附带参数),其实就是上面的“信号”概念。
6.信号有了,需要将信号发给接受者,那么,DispatchSignal方法就负责将信号发给对应的Rect对象(通过rect.ID ?= signal.ID来判断)。
7.接受者(Rect对象)使用自己的RectProc来处理信号,RectProc方法中根据不同的信号类型,作出相应的反应。
可能文字不太直观,上一张图,来解释一下,图文结合更有效。

 
好了,改了之后的代码复杂很多,当然了,功能也比之前的多了很多,但是还是那句话,大概结构没有变,一个while循环、一个接收用户输入部分、一个响应用户操作部分。(看完代码和图的同学,或者说有Win32编程基础的同学,到现在为止,可能已经看出这是个啥意思,我们暂且先不说,听我慢慢道来O(∩_∩)O~)
现在我来说说改了之后的代码还有哪些地方的不足:

1.每个Rect对象之间无法通信,因为各个Rect对象之间是相互独立的,每个Rect对象在响应用户输入(执行RectProc,下同)的时候不能影响其他的Rect对象,因为你根本不知道另外的Rect对象在哪、什么状态。
2.在响应用户输入的时候,也就是while循环体执行期间,我们不能改变while循环条件,让循环结束,意思就是,现在这个线程,有两种情况退出,第一种就是用户直接输入“QUIT”,第二种就是强制关闭程序,后者明显不可取,那么前者一种方法能满足我们的需求吗?答案是不能,现在考虑这种情况:在线程运行期间,所有存在的Rect对象中有一个是主Rect,也就是说,这个主Rect对象跟其他不一样,当这个主Rect对象被用户关闭后(RS_KILL),最好的效果就是,整个线程结束。因此,在主Rect对象处理RS_KILL信号后,应立马“模仿”用户向线程再发送一个“QUIT”字符串,让while循环下一次退出。
3.Rect对象既然是用户主要操作的目标,那么就应该允许我们在Rect类上继承新的类,来实现更丰富的效果,而且,新扩展出来的类也应该像Rect类一样响应用户输入。
4.同样,Rect类对象的一举一动,势必会影响另外一些对象,所以,Rect类应该加上一些事件(此处事件为.net中的Event,它与Windows事件的区别稍后会讲)。
5.在Rect类对象响应用户的某一次操作后,可能需要再次通知自己进行其他操作,比如一个Rect对象在响应“改变位置”这个信号之后,立马需要显示自己信息,也就是说在处理完RS_POSITIONCHANGE信号后,立刻需要给自己发一个RS_SHOWINFO信号,它才能显示自己的信息。这就出现一个问题,“信号”会产生“信号”,这个过程完全不需要用户区操控,当然,用户也无法去操控。
6.最后,不知道诸位发现没有,用户的输入与Rect对象的响应是(也只能是)同步的,啥叫同步?简单来说就是,A做完什么之后,B才能行动,或者等B行动完后,A才能继续。只有等用户输入后,GetSignal方法才能返回,Rect对象才能做出反应,同理,只有Rect对象响应完成后,用户才可能继续输入,一次输入一次响应,输入没完成,就没有响应,响应没完成,用户也不能输入。理想情况应该是这样的:用户在想要输入的时候就可以输入,而不用去管Rect对象有没有响应完成(DispatchSignal返回),当然,在这种情况下,用户的输入仍然会陆陆续续的被响应。
分析一下上面6条,其中1、2、5条其实意思差不多,就是在while循环体执行期间,需要“模仿”用户输入,然而现在的情况是,GetSignal方法是“主动型”的,只有它主动去接收用户输入,它才会有结果,当它没有准备好,就算有输入,也不会被接收。这样看来,我们只有增加一个类似“缓冲区”的东西,不管GetSignal有没有准备,所有的输入信号全部存放在这个缓冲区中,等到GetSignal准备好获取输入信号时,直接从这个缓冲区中取得。
说到“缓冲区”,我们第一应该想到用“队列”,不错,就是队列!我们来看一下MSDN上对“队列”(Queue类)的解释:
Queues are useful for storing messages in the order they were received for sequential processing. This class implements a queue as a circular array. Objects stored in a Queue are inserted at one end and removed from the other.
大概意思就是队列一般用于存储需要按顺序处理的消息。
第6条其实也可以用队列来实现,用户不停地向队列输入,而不用管Rect对象是否立刻去响应,队列起到一个缓冲的作用,当然,如果这样设计的话,用户输入和Rect对象响应输入应该不在同一线程,这就要用到多线程了。
第3、4条其实就是OO中的继承、虚方法引起的“多态性”,以及.net中常用到的Observer模式,用Event很好实现。
升华:
经过分析,Rect类改为:(虚方法以及事件只举例定义了两个,现实中应该有很多个)

代码如下:

View Code
///矩形类
Class Rect
{
int _id; //矩形唯一标示
string _text; //矩形中心显示的文本
Size _size; //矩形大小
Point _location; //矩形的位置
Bool _alive; //是否存活
Public int ID
{
Get
{
Return _id
}
Set
{
_id = value;
}
}
Public string Text
{
Get
{
Return _text;
}
Set
{
_text = value;
}
}
Public Size Size
{
Get
{
Return _size;
}
Set
{
_size = value;
}
}
Public Point Location
{
Get
{
Return _location;
}
Set
{
_location = value;
}
}
Public bool Alive
{
Get
{
Return _alive;
}
Set
{
_alive = value;
}
}
Public Rect(int id,string text,Size size,Point location)
{
_id = id;
_text = text;
_size = size;
_location = location;
_alive = true;
}
//矩形对外唯一接口,对矩形的所有操作必须调用此方法,下称“矩形过程”
Public virtual void RectProc(int id,int type,object leftParam,object rightParam)
{
Switch(type) //这个type就是后面说的“信号类型”, 应该跟Sgl枚举一一对应
{
Case 1: //移动、改变大小
{
Size newSize = (Size)leftParam;
Position newLocation = (Point)rightParam;
This.Size = newSize;
This.Location = newLocation;
OnPositionChanged(new PositionChangedEventArgs(this.Size,this.Location));
Break;
}
Case 2: //显示信息
{
//调用对应虚方法
Break;
}
Case 3: //关闭
{
Alive = false;
OnKill(new EventArgs());
Break;
}
//……很多省略
Default:
{
//默认处理
}
}
}
Protected virtual void OnPositionChanged(PositionChangedEventArgs e)
{
If(PositionChanged!=null)
{
PositionChanged(this,e);
}
}
Protected virtual void OnKill(EventArgs e)
{
If(Kill!=null)
{
Kill(this,e);
}
}
Public event PositionChangedEventHandler PositionChanged;
Public event EventHandler Kill;
}

还需添加以下委托和类:
代码如下:

View Code
Public delegate void PositionChangedEventHandler(object sender,PositionChangedEventArgs e)
Public class PositionChangedEventArgs
{
Size _size;
Point _location;
Public Size Size
{
Get
{
Return _size;
}
}
Public Point Location
{
Get
{
Return _location;
}
}
Public PositionChangedEventArgs(Size size,Point location)
{
_size = size;
_location = location;
}
}

再从Rect类派生出一个新的类DeriveRect,该类为Rect类的子类:
代码如下:

View Code
class DeriveRect:Rect
{
public DeriveRect(int id,string text,Size size,Point location):base(id,text,size,location)
{
}
public override void RectProc(int id,int type,object leftParam,object rightParam)
{
//拦截信号
base.RectProc(int id,int type,object leftParam,object rightParam);
}
protected override void OnPositionChanged(PositionChangedEventArgs e)
{
//添加自己的代码
//ZZThread.SendSignal(…)发送信号到本线程信号队列中
base.OnPositionChanged(e); //触发基类事件
}
//你可以重写其他虚方法,就像继承一个Form类,重写它的虚方法一样。
}

为了统一处理信号,我们在Sgl枚举类型中再加一个枚举变量RS_QUIT,它指示while循环退出(这个信号不再唯一由用户输入,为什么请看前面提出的6条),Sgl枚举改为:
代码如下:

View Code
Enum Sgl
{
RS_POSITIONCHANGE = 1, //移动矩形,大小变化
RS_SHOWINFO = 2, //矩形显示自己信息
RS_KILL = 3, //关闭矩形
RS_QUIT = 4 //退出循环
//……很多省略
}

ZZThread类则改为:(主要增加了一个信号列表,然后修改了一下GetSignal方法,让其直接从信号列表中获取信号,而不需要再等待用户输入,当然,我这里没有写出专门让用户输入的线程,因为这个示意性代码本身就是一个Console程序,多线程去接收用户输入的话,“输入内容”会和“响应用户输入的内容”相混淆,只需要知道用户会在另外一个线程中向signalList中添加信号,而这个动作不需要我们有多少了解,原因后面会讲到)。另外,现在用户可以操作的不单单是Rect类对象了,可以是Rect类的派生类,而且你还可以监听Rect类(或其派生类的事件)。为了在循环体执行期间,控制循环退出,增加了一个PostQuit方法,该方法只是简单的向signalList队列添加一个“退出”信号。
代码如下:

View Code
/// 主线程
/// 测试代码
Static class ZZThread
{
Static List allRects = new List(); //整个线程运行过程中,存在的Rect对象
Static Queue signalList = new Queue(); //线程信号队列(考虑到另有线程接收用户输入,也会操作此信号队列,所以请考虑线程同步问题)
//线程入口
Public Static void Main()
{
//初始化一个Rect对象,一个为DeriveRect对象,前者为主Rect,当它关闭的时候,退出循环,结束线程
Rect chiefRect = new Rect(1,”I am the chief Rect!”,new Size(100,100),new Point(10,10));
chiefRect.Kill += (EventHandler)(delegate(sender,e){PostQuit();}); //监听主Rect的关闭事件,向所在线程信号队列发送一个退出信号
Rect derivedRect = new DeriveRect(2,”I am the Derived Rect”,new Size(150,150),new Point(200,200));
allRects.Add(chiefRect);
allRects.Add(derivedRect);
//开始循环从线程的信号队列里获取信号
Signal signal = null;
While(GetSignal(out signal)) //接收信号
{
DispatchSignal(signal); //分配信号到各个Rect
}
Console.WriteLine(“The Thread Exit”);
// Console.ReadKey(); //阻塞查看运行情况
}
Static bool GetSignal(out Signal signal)
{
//从队列获取信号,如果队列为空,阻塞直到队列有信号为止,否则如果非RS_QUIT,返回true,如果RS_QUIT,则返回false
START:
If(signalList.Count!=0) //注意需要处理线程同步
{
signal = signalList.DeQueue() as Signal;
if(signal.Type != (int)Sgl.RS_QUIT)
{
return true;
}
else
{
return false;
}
}
Else
{
Thread.Sleep(1);
goto START;
}
}
Static void DispatchSignal(Signal signal)
{
Foreach(Rect rect in allRects)
{
If(rect.ID == signal.ID && rect.Alive)
{
rect.RectProc(signal.ID,signal.Type,signal.LeftParam,signal.RightParam);
break;
}
}
}
public static void PostQuit()
{
signalList.EnQueue(new Signal(0,(int)Sgl.RS_QUIT,null,null));
}
public static void SendSignal(int id,int type,object leftParam,object rightParam)
{
signalList.EnQueue(new Signal(id,type,leftParam,rightParam));
}
}

好了,改完了,解释一下改完后的代码。改完后的代码和之前的大概结构仍然相同,一个while循环、一个获取信号(这里不再单单是用户输入了,还包括循环体内向循环体外发送的信号)的GetSignal方法、一个处理信号(将信号分发给线程中对应的Rect对象以及其派生类)的DispatchSignal方法。
再上张图,图文结合,效果杠杠的。


再分析一下,代码修改之前提出的6条不足,基本上全部解决了
1.在Rect对象(或其派生类对象,下同)处理信号的时候,只要知道任何一个相同线程中的其它Rect对象的ID,那么就可以利用ZZThread.SendSignal()向其发送信号。
2.同理,在某一Rect对象(我们在这成为主Rect)关闭的时候(处理RS_QUIT信号),它可以通过ZZThread.PostQuit()方法向循环发送退出信号。
3.通过允许Rect类被继承,就可以实现多样化的效果,响应用户输入不再仅仅只是Rect类了,还可以是Rect的派生类。
4.通过向响应者(Rect类)添加事件(Event)的方法,外部其他Rect对象就可以监听到事件的发生,做出相应响应。
5.与1相似,在处理某一信号的时候,完全可以通过ZZThread.SendSignal方法将ID参数设为自己的ID,向自己发送一个信号,这就可以达到“在处理完一个信号后紧接着向自己发送另外一个信号”的功能。
6.通过增加信号队列和一个专门接受用户输入的线程(以上示意性代码中未给出),完全可以达到“让用户输入和Rect对象响应”异步发生。
以上6条确实完美解决了,现在继续考虑几个问题(你们可能知道改完之后的这个东西肯定不会是我们最终想要的,因为它貌似跟我要讲的Winform几乎没有任何联系,所以,继续考虑下面几个问题)。
(由于每个问题跟上一个问题有联系,所以我依次给出了问题的解决办法。)
1. 这个只能在一个线程中使用,也就是说,同一个程序中,只能存在一个这样的线程,因为ZZThread类是个静态类,所有的成员也是静态的,如果多个线程使用它的话,就会全乱了。举例看下面:
Thread th1 = new Thread((ThreadStart)(delegate(){
ZZThread.Main();
}));
th1.Start();
Thread th2 = new Thread((ThreadStart)(delegate(){
ZZThread.Main();
}));
th2.Start();
th1与th2两个线程中,用到的signalList、allRects是同一个,两个线程中的while循环也是从同一个信号队列中去取信号,然后分配给同一个Rect对象集合中的对象,虽然可以做一些同步“线程安全”的处理,但是仍然有问题,仔细想一想(比如发送RS_QUIT信号想让本线程退出,到底哪个退出不确定)。因此,理想情况应该是这样的:每一个线程有自己的信号队列(signalList),有自己的Rect对象集合(allRects),有自己的while循环和自己的DispatchSignal方法,换句话说,两个线程之间不应该有瓜葛,而应该互不影响,相互独立。(当然,除了这些,两个线程理论上可以有其他联系,后面会提到)。
解决方法:
既然ZZThread类是静态的,那么我们就可以把它设置成非静态,每个线程对应一个ZZThread对象,这样线程与线程之间就不会有影响,每个线程都有自己的信号队列、自己的Rect对象集合以及自己的while循环和DispatchSignal方法。当然,如果这样处理的话,就应该考虑怎么确保每个线程拥有自己的ZZThread对象,就是说,怎么保证一个线程能找到与它对应的ZZThread对象?很简单,每个线程都有唯一一个ID(整个系统范围内唯一),可以定义一个Dictionary字典,在每个线程中要使用ZZThread对象的地方,先根据线程ID(这个可以随时取得,只要在同一线程中,ID肯定相同)查找字典,如果存在,直接拿出来使用,如果不存在,说明还没有创建,那就新建一个ZZThread对象,加到字典中,将新建的ZZThread对象拿来使用。这样的话,在每个线程的任何一个地方要使用ZZThread对象的话,都能通过该方法取得同一个ZZThread对象。但要考虑怎么去维护这样一个字典?
2. ZZThread类不能直接暴露给使用者。还是考虑多个线程的情况,ZZThread类中的while循环入口(之前一直是Main方法)、以及诸如像PostQuit、SendSignal等(后面可能还会增加)都是public类型的,如果ZZThread直接暴漏给使用者,使用者完全可以在一个线程中使用另外一个ZZThread对象(注:1中的解决方法只解决了“怎样让一个线程正确地使用同一个ZZThread对象”,并没有解决“一个线程只能使用一个ZZThread对象”)。
解决方法:
一个很好的解决方法就是将“精髓部分”封装起来,封装成一个库(或者模块、框架随便叫),只对外开放必要的类,而像ZZThread这样的类也就没必要开放,最关键的是,1中提到的字典也不应该对外开放,使用者的不正当操作很可能破坏该字典。
3. 考虑一种情况,Rect对象在响应信号时(RectProc执行期间),耗时时间太长,即DispatchSignal方法长时间不能返回,也就是说,长时间不能再次调用GetSignal方法,导致线程中信号大量累积,不能及时处理,因此用户的输入也不会及时得到响应,造成用户体验明显下降,这时候改怎么处理?
解决方法:
既然DispatchSignal方法不能及时返回,导致信号队列的信号不能即使被处理,那么我们可以在Rect对象处理信号的耗时操作中(RectProc执行期间),执行适当次数的while循环,也就是说,在一个while循环体内,再次执行一个while循环,及时处理信号,这就是嵌套执行while循环了,当然,内部的while循环跟外部的while循环有稍微差别,即内部while循环每次就执行一次,执行完后,继续做其他的耗时操作,如果需要大量循环处理一个内容,可以在每次循环结束后调用一次while循环,保证信号队列的信号能够及时处理。上一张图,看得明白一些:


另外,还有一种内嵌的while循环,它不止执行一次循环就退出,而是当某种条件为真时,才退出,这种现在涉及不到。总之,我们可以看出一个线程中可以有多个while循环来处理信号,这些while循环大多是嵌套调用的(不排除这种情况:一个while循环退出后,接着再跟一个while循环,这两个while循环完全相同,但这种出现的几率很少,以后讲到Winform相关的时候我会谈到这个东西的)。由此可以看出,一个线程中的while循环有好几种,所以我们需要每次调用while循环的时候加以区别。
4. 由1中得知一个应用程序可能由好几个需要处理信号的线程组成,每个线程之间相互独立,由2中得知,需要将不必要的类型封装起来,只向用户提供部分类型,使用者利用仅有提供的公开类型就可以写出各种各样自己想要的效果。既然要将代码关键部分与用户可扩展部分分开,那么现在就要分清哪些东西不需要使用者操心、而哪些则需要使用者操心。
解决方法:
前面说过,ZZThread类肯定不能公开,也就是说while循环、信号队列、Rect对象集合都不公开,Sgl枚举和Signal类也没必要公开,因为使用者根本不需要知道这些东西(从框架设计角度,这些东西也不是公开的),使用者唯一需要了解的就是Rect类,使用者知道了Rect类之后,就可以从该类派生出各种各样的子类,在子类中重写Rect的虚方法,然后注册子类的一些事件等等(这要求必须提前将Rect类中需要处理的信号考虑完整,也就是说一切信号类型在Rect类中全部都有默认处理,而且还要完善虚方法,子类才能重写任何一个它想重写的虚方法)。另外,如果ZZThread类不公开,那么使用者怎么让线程进入while循环?因此,需要定义一个代理类,该代理类跟普通类不一样(见代码),然后将该代理类公开给使用者。
精包装:
框架部分
(1)ZZAplication代理类

代码如下:

View Code
public class ZZApplication
{
public static event EventHandler ApplicationExit;
public static event EventHandler ThreadExit;
//用一个Rect对象作为主Rect,开启信号循环
public static void Start(Rect rect)
{
ZZThread.FromCurrent().StartLoop(1,rect);
}
//执行一次信号循环,进行一次信号处理
public static void DoThing()
{
ZZThread.FromCurrent().StartLoop(2,null);
}
internal static void RaiseThreadExit()
{
if(ThreadExit!=null)
{
ThreadExit(null,EventArgs.Empty);
}
}
internal static void RaiseApplicationExit()
{
if(ApplicationExit!=null)
{
ApplicationExit(null,EventArgs.Empty);
}
}
}

(2)ZZThread类
代码如下:

View Code
internal class ZZThread
{
private Queue signalList = new Queue();
private List allRects = new List();
private int mySignalLoopCount = 0;
private static totalSignalLoopCount = 0;
private static Dictionary allZZThreads = new Dictionary();
public static ZZThread FromCurrent() //获取与当前线程相关联的ZZThread对象
{
int id = Thread.CurrentThread.ManagedThreadId;
if(allThreads.ContainKey(id))
{
return allThreads[id];
}
else
{
ZZThread zzthread = new ZZThread();
allZZThreads.Add(id,zzthread);
return zzthread;
}
}
//开始一个信号循环,1表示第一层循环,2表示第二层循环
public void StartLoop(int reason, Rect rect)
{
mySignalLoopCount++;
totalSignalLoopCount++;
if(reason == 1)
{
if(mySignalLoopCount!=1)
{
return; //不能嵌套调用第一层循环
}
rect.Kill+=(EventHandler)(delegate(sender,e){PostQuit();});
}
Signal signal = null;
while(GetSignal(out signal))
{
DispatchSignal(signal);
if(reason == 2)
{
break;
}
}
mySignalLoopCount--;
totalSignalCount--;
if(reason == 1) //退出外层循环
{
Dispose();
}
}
public void AddRect(Rect rect)
{
allRects.Add(rect);
}
public void Dispose()
{
if(mySignalCount != 0)
{
PostQuit();
}
else
{
ZZApplication.RaiseThreadExit();
if(totalSignalLoopCount==0)
{
ZZApplication.RaiseApplicationExit();
}
}
}
public void SendSignal(int id,int type,object leftParam,rightParam)
{
signalList.EnQueue(new Signal(id,type,leftParam,rightParam));
}
private bool GetSignal(out Signal signal)
{
START:
If(signalList.Count!=0) //注意需要处理线程同步
{
signal = signalList.DeQueue() as Signal;
if(signal.Type != (int)Sgl.RS_QUIT)
{
return true;
}
else
{
return false;
}
}
Else
{
Thread.Sleep(1);
goto START;
}
}
private void DispatchSignal(Signal signal)
{
Foreach(Rect rect in allRects)
{
If(rect.ID == signal.ID && rect.Alive)
{
rect.RectProc(signal.ID,signal.Type,signal.LeftParam,signal.RightParam);
break;
}
}
}
private void PostQuit()
{
signalList.EnQueue(new Signal(0,(int)RS_QUIT,null,null));
}
}

(3)Rect类(其他派生自Rect)
代码如下:

View Code
public class Rect
{
int _id; //矩形唯一标示
string _text; //矩形中心显示的文本
Size _size; //矩形大小
Point _location; //矩形的位置
Bool _alive; //是否存活
Public int ID
{
Get
{
Return _id
}
Set
{
_id = value;
}
}
Public string Text
{
Get
{
Return _text;
}
Set
{
_text = value;
}
}
Public Size Size
{
Get
{
Return _size;
}
Set
{
_size = value;
}
}
Public Point Location
{
Get
{
Return _location;
}
Set
{
_location = value;
}
}
Public bool Alive
{
Get
{
Return _alive;
}
Set
{
_alive = value;
}
}
Public Rect(int id,string text,Size size,Point location)
{
_id = id;
_text = text;
_size = size;
_location = location;
_alive = true;
ZZThread.FromCurrent().AddRect(this); //添加到创建自己的线程的ZZThread对象中
}
public Rect()
{
_id = GetUID() // 获取Rect的全局唯一标示
_text = “”;
_size = new Size(100,100);
_location = new Point(100,100);
_alive = true;
ZZThread.FromCurrent().AddRect(this); //添加到创建自己的线程的ZZThread对象中
}
//矩形对外唯一接口,对矩形的所有操作必须调用此方法,下称“矩形过程”
Public virtual void RectProc(int id,int type,object leftParam,object rightParam)
{
Switch(type) //这个type就是后面说的“信号类型”, 应该跟Sgl枚举一一对应
{
Case 1: //移动、改变大小
{
Size newSize = (Size)leftParam;
Position newLocation = (Point)rightParam;
This.Size = newSize;
This.Location = newLocation;
OnPositionChanged(new PositionChangedEventArgs(this.Size,this.Location));
Break;
}
Case 2: //显示信息
{
Break;
}
Case 3: //关闭
{
Alive = false;
OnKill(new EventArgs());
Break;
}
//……很多省略
Default:
{
//默认处理
}
}
}
Protected virtual void OnPositionChanged(PositionChangedEventArgs e)
{
If(PositionChanged!=null)
{
PositionChanged(this,e);
}
}
Protected virtual void OnKill(EventArgs e)
{
If(Kill!=null)
{
Kill(this,e);
}
}
Public event PositionChangedEventHandler PositionChanged;
Public event EventHandler Kill;
}

(4)委托等事件参数类
代码如下:

View Code
Public delegate void PositionChangedEventHandler(object sender,PositionChangedEventArgs e)
Public class PositionChangedEventArgs
{
Size _size;
Point _location;
Public Size Size
{
Get
{
Return _size;
}
}
Public Point Location
{
Get
{
Return _location;
}
}
Public PositionChangedEventArgs(Size size,Point location)
{
_size = size;
_location = location;
}
}

(5)信号类
代码如下:

View Code
internal class Signal
{
Int _id; //接受者id
Int _type; //信号类型
Object _leftParam; //参数1
Object _rightParam; //参数2
Public int ID
{
Get
{
Return _id;
}
Set
{
_id = value;
}
}
Public int Type
{
Get
{
Return _type;
}
Set
{
_type = value;
}
}
Public object LeftParam
{
Get
{
Return _leftParam;
}
Set
{
_leftParam = value;
}
}
Public object RightParam
{
Get
{
Return _rightParam;
}
Set
{
_rightParam = value;
}
}
Public Signal(int id,int type,object leftParam,object rightParam)
{
_id = id;
_type = type;
_leftParam = leftParam;
_rightParam = rightParam;
}
}

(6)Sgl枚举类型
代码如下:

View Code
internal Enum Sgl
{
RS_POSITIONCHANGE = 1, //移动矩形,大小变化
RS_SHOWINFO = 2, //矩形显示自己信息
RS_KILL = 3, //关闭矩形
RS_QUIT = 4 //退出
//……很多省略
}

客户端:
代码如下:

View Code
static class Program
{
///
/// 应用程序的主入口点。
///
[STAThread]
static void Main()
{
ZZApplication.ApplicationExit += new EventHandler(ZZApplication_ApplicationExit);
ZZApplication.ThreadExit += new EventHandler(ZZApplication_ThreadExit);
ZZApplication.Start(new Rect1());
}
static void ZZApplication_ApplicationExit(object sender,EventArgs e)
{
//应用程序退出
}
static void ZZApplication_ThreadExit(object sender,EventArgs e)
{
//单个信号处理线程退出
}
}
//定义一个新的Rect派生类
class Rect1:Rect
{
public Rect1(int id,string text,Size size,Point location):base(id,text,size,location)
{
Init();
}
public Rect1()
{
Init();
}
private void myChildRect_Kill(object sender,EventArgs e)
{
//大循环耗时计算,不能及时返回
for(int i=0;i

    
 
 

您可能感兴趣的文章:

 
本站(WWW.)旨在分享和传播互联网科技相关的资讯和技术,将尽最大努力为读者提供更好的信息聚合和浏览方式。
本站(WWW.)站内文章除注明原创外,均为转载、整理或搜集自网络。欢迎任何形式的转载,转载请注明出处。












  • 相关文章推荐
  • java命名空间javax.net类socketfactory的类成员方法: createsocket定义及介绍
  • .NET版的ExtJS库 Ext.Net
  • java命名空间java.net类datagramsocket的类成员方法: disconnect定义及介绍
  • node.js的.net扩展 node.net
  • java命名空间java.net类datagramsocket的类成员方法: close定义及介绍
  • 为什么输http://www.china-java.net,会自动改为http://www.china-java.net:8081?
  • java命名空间java.net接口cookiestore的类成员方法: get定义及介绍
  • 各位之不知道net-snmp是否收费?我的产品中用到了net-snmp lib是否需要向什么单位或者组织付费?
  • java命名空间java.net类socket的类成员方法: isbound定义及介绍
  • 【人才】有没有人会用VC6.0/VS2003.NET/VS2005.NET写WINDOWS下的驱动程序呀。
  • java命名空间java.net类datagrampacket的类成员方法: getsocketaddress定义及介绍
  • Java.NET or J#.NET is coming!
  • java命名空间java.net类multicastsocket的类成员方法: getinterface定义及介绍
  • make menuconfig时出错:net/Kconfig:221:can't open file "net/wireless/Kconfig"
  • java命名空间java.net枚举proxy.type的类成员方法: http定义及介绍
  • 用过net-snmp(ucd-snmp)的大侠用过net-snmp(ucd-snmp)请进(来者有分)
  • java命名空间java.net类urisyntaxexception的类成员方法: getreason定义及介绍
  • 常用.NET工具(包括.NET可再发行包2.0)下载
  • java命名空间java.net类datagramsocketimpl的类成员方法: getlocalport定义及介绍
  • Ja.Net
  • java命名空间java.net类httpretryexception的类成员方法: getreason定义及介绍
  • asp.net判断数据库表是否存在 asp.net修改表名的方法




  • 特别声明:169IT网站部分信息来自互联网,如果侵犯您的权利,请及时告知,本站将立即删除!

    ©2012-2021,,E-mail:www_#163.com(请将#改为@)

    浙ICP备11055608号-3