您好,欢迎访问三七文档
当前位置:首页 > 电子/通信 > 综合/其它 > 第1章 进程、线程与网络协议
1第1章进程、线程与网络协议在Windows应用程序中,基于客户端和服务器的各种网络编程技术应用非常广泛,本书将逐步介绍这类应用程序的编程方法。在《C#网络应用编程基础》(马骏主编,人民邮电出版社)一书中,我们已经学习了C#面向对象编程的基础知识以及基本控件的用法。本书假定读者对相关基本内容比较熟悉,并在此基础上,进一步学习更为复杂的基于Windows窗体的高级应用开发,而不再介绍C#语法以及控件的用法等基础内容。如果读者对C#相关知识不太熟悉,请首先阅读《C#网络应用编程基础》一书。本章主要介绍进程、线程、IP地址与端口、套接字以及网络流等基本知识,这部分内容是学习本书后面章节的基础,希望读者能很好的理解和掌握。1.1进程和线程进程是对一段静态指令序列(程序)的动态执行过程,是系统进行资源分配和调度的一个基本单位。与进程相关的信息包括进程的用户标志、正在执行的已经编译好的程序、进程程序和数据在存储器中的位置等等。同一个进程又可以划分为若干个独立的执行流,我们称之为线程。线程是CPU调度和分配的基本单位。在Windows环境下,用户可以同时运行多个应用程序,每个执行的应用程序就是一个进程。例如一台电脑上同时打开两个QQ时,每个运行的QQ就是一个进程;而用一个QQ和多个人聊天时,每个聊天窗口就是一个线程。进程和线程概念的提出,对提高软件的并行性有着重要的意义。并行性的主要特点就是并发处理。在一个单处理器系统中,可以通过分时处理来获得并发,这种情况下,系统为每个线程分配一个CPU时间片,每个线程只有在分配的时间片内才拥有对CPU的控制权,其他时间都在等待。即同一时间只有一个线程在运行。由于系统为每个线程划分的时间片很小(20毫秒左右),所以在用户看来,好像是多个线程在同时运行。为什么要使用多线程呢?考虑这样一种情况:在C/S模式下,服务器需要不断监听来自各个客户端的请求,这时,如果采用单线程机制的话,服务器将无法处理其他事情,因为这个线程要不断的循环监听请求而无暇对其他请求做出响应。实际上,当要花费大量时间进行连续的操作时,或者等待网络或其他I/O设备响应时,都可以使用多线程技术。在C#中,有两个专门用于处理进程和线程的类:Process类和Thread类。1.1.1Process类Process类位于System.Diagnostics命名空间下,用于完成进程的相关处理任务。可以在本地计算机上启动和停止进程,也可以查询进程的相关信息。在自己的程序中运行其他的应用程序,实际上就是对进程进行管理。如果希望在自己的进程中启动和停止其他进程,首先要创建Process类的实例,并设置对象的StartInfo属性,然后调用该对象的Start方法启动进程。第1章进程、线程与网络协议2【例1-1】启动、停止和观察进程。(1)新建一个名为ProcessExample的Windows应用程序,设计界面如图1-1所示。(2)展开工具箱中的【组件】选项卡,然后将Process组件拖放到设计窗体上。(3)在代码的开始部分添加命名空间引用:usingSystem.Diagnostics;usingSystem.Threading;(4)分别在【启动记事本】、【停止记事本】和【观察所有进程】三个按钮的Click事件中添加代码:privatevoidbuttonStart_Click(objectsender,EventArgse){process1.StartInfo.FileName=notepad.exe;//启动Notepad.exe进程.process1.Start();}privatevoidbuttonStop_Click(objectsender,EventArgse){//创建新的Process组件的数组,并将它们与指定的进程名称(Notepad)的所有进程资源相关联.Process[]myprocesses;myprocesses=Process.GetProcessesByName(Notepad);foreach(Processinstanceinmyprocesses){//设置终止当前线程前等待1000毫秒instance.WaitForExit(1000);instance.CloseMainWindow();}}privatevoidbuttonView_Click(objectsender,EventArgse){listBox1.Items.Clear();//创建Process类型的数组,并将它们与系统内所有进程相关联Process[]processes;processes=Process.GetProcesses();foreach(Processpinprocesses){//Idle指显示CPU空闲率的进程名称//由于访问Idle的StartTime会出现异常,所以将其排除在外if(p.ProcessName!=Idle){//将每个进程名和进程开始时间加入listBox1中this.listBox1.Items.Add(string.Format({0,-30}{1:h:m:s},p.ProcessName,p.StartTime));}}}(5)按F5键编译并执行,单击几次【启动记事本】按钮,观察打开的每个进程,然后单击【停止记事本】,观察依次停止进程的情况。图1-1测试进程界面第1章进程、线程与网络协议3由于安装Windows操作系统后,notepad.exe就已经安装到系统文件夹下,而且在任何一个文件夹中均可以直接运行,所以在这个例子中,我们选择了调用notepad.exe作为演示的例子。实际上,任何一个可执行文件均可以通过这种方法调用,读者可以自行尝试调用其他可执行文件,并观察执行效果。1.1.2Thread类在System.Threading命名空间下,包含了用于创建和控制线程的Thread类。对线程的常用操作有:启动线程、终止线程、合并线程和让线程休眠等。1.启动线程在使用线程前,首先要创建一个线程。其一般形式为:Threadt=newThread(enterPoint);其中enterPoint为线程的入口,即线程开始执行的方法。在托管代码中,通过委托处理线程执行的代码。例如:Threadt=newThread(newThreadStart(methodName));创建线程实例后,就可以调用Start方法启动线程了。2.终止线程线程启动后,当不需要某个线程继续执行的时候,有两种终止线程的方法。一种是事先设置一个布尔变量,在其他线程中通过修改该变量的值作为传递给该线程是否需要终止的判断条件,而在该线程中循环判断该条件,以确定是否退出线程,这是结束线程的比较好的方法,实际编程中一般使用这种方法。第二种方法是通过调用Thread类的Abort方法强行终止线程。例如:t.Abort();Abort方法没有任何参数,线程一旦被终止,就无法再重新启动。由于Abort通过抛出异常强行终止结束线程,因此在实际编程中,应该尽量避免采用这种方法。调用Abort方法终止线程时,公共语言运行库(CLR)会引发ThreadAbortException异常,程序员可以在线程中捕获ThreadAbortException异常,然后在异常处理的Catch块或者Finally块中作释放资源等代码处理工作;但是,线程中也可以不捕获ThreadAbortException异常,而由系统自动进行释放资源等处理工作。注意,如果线程中捕获了ThreadAbortException异常,系统在finally子句的结尾处会再次引发ThreadAbortException异常,如果没有finally子句,则会在Catch子句的结尾处再次引发该异常。为了避免再次引发异常,可以在finally子句的结尾处或者Catch子句的结尾处调用System.Threading.Thread.ResetAbort方法防止系统再次引发该异常。使用Abort方法终止线程,调用Abort方法后,线程不一定会立即结束。这是因为系统在结束线程前要进行代码清理等工作,这种机制可以使线程的终止比较安全,但清理代码需要一定的时间,而我们并不知道这个工作将需要多长时间。因此,调用了线程的Abort方法后,如果系统自动清理代码的工作没有结束,可能会出现类似死机一样的假象。为了解决这个问题,可以在主线程中调用子线程对象的Join方法,并在Join方法中指定主线程等待子线程结束的等待时间。3.合并线程Join方法用于把两个并行执行的线程合并为一个单个的线程。如果一个线程t1在执行的第1章进程、线程与网络协议4过程中需要等待另一个线程t2结束后才能继续执行,可以在t1的程序模块中调用t2的join()方法。例如:t2.Join();这样t1在执行到t2.Join()语句后就会处于阻塞状态,直到t2结束后才会继续执行。但是假如t2一直不结束,那么等待就没有意义了。为了解决这个问题,可以在调用t2的Join方法的时候指定一个等待时间,这样t1这个线程就不会一直等待下去了。例如,如果希望将t2合并到t1后,t1只等待100毫秒,然后不论t2是否结束,t1都继续执行,就可以在t1中加上语句:t2.Join(100);Join方法通常和Abort一起使用。由于调用某个线程的Abort方法后,我们无法确定系统清理代码的工作什么时候才能结束,因此如果希望主线程调用了子线程的Abort方法后,主线程不必一直等待,可以调用子线程的Join方法将子线程连接到主线程中,并在连接方法中指定一个最大等待时间,这样就能使主线程继续执行了。4.让线程休眠在多线程应用程序中,有时候并不希望某一个线程继续执行,而是希望该线程暂停一段时间,等待其他线程执行之后再继续执行。这时可以调用Thread类的Sleep方法,即让线程休眠。例如:Thread.Sleep(1000);这条语句的功能是让当前线程休眠1000毫秒。注意,调用Sleep方法的是类本身,而不是类的实例。休眠的是该语句所在的线程,而不是其他线程。5.线程优先级当线程之间争夺CPU时间片时,CPU是按照线程的优先级进行服务的。在C#应用程序中,可以对线程设定五个不同的优先级,由高到低分别是Highest、AboveNormal、Normal、BelowNormal和Lowest。在创建线程时如果不指定其优先级,则系统默认为Normal。假如想让一些重要的线程优先执行,可以使用下面的方法为其赋予较高的优先级:Threadt=newThread(newThreadStart(enterpoint));t.priority=ThreadPriority.AboveNormal;通过设置线程的优先级可以改变线程的执行顺序,所设置的优先级仅仅适用于这些线程所属的进程。注意,当把某线程的优先级设置为Highest时,系统上正在运行的其他线程都会终止,所以使用这个优先级别时要特别小心。6.线程池线程池是一种多线程处理形式,为了提高系统性能,在许多地方都要用到线程池技术。例如,在一个C/S模式的应用程序中的服务器端,如果每到一个请求就创建一个新线程,然后在新线程中为其请求服务的话,将不可避免的造成系统开销的增大。实际上,创建太多的线程可能会导致由于过度使用系统资源而耗尽内存。为了防止资源不足,服务器端应用程序应采取一定的办法来限制同一时刻处理的线程数目。线程池为线程生命周期的开销问题和资源不足问题提供了很好的解决方案。通过对多个任第1章进程、线程与网络协议5务重用线程,线程创建的开销被分摊到了多个任务上。其好处是,由于请求到达时线程已经存在,所以无意中也就消除了线程创建所带来的延迟。这样,就可以立即为新线程请求服务,使其应用程序响应更快。而且,通过适当地调整线程池中的线程数目,也就是当请求的数目超过了规定的最大数目时,就强制其他任何新到的请求一直等待,直到获得一个线程来处理为止,从而可以防止资源不足。线程池适用于需要多个线程而实际执行时间又不多的场合,比如有些
本文标题:第1章 进程、线程与网络协议
链接地址:https://www.777doc.com/doc-3403890 .html