《第十六部分多线程处理教学课件.ppt》由会员分享,可在线阅读,更多相关《第十六部分多线程处理教学课件.ppt(69页珍藏版)》请在三一办公上搜索。
1、第十六章 多线程处理,本章的学习内容包括六个方面:1理解多线程处理概念。2理解多线程处理怎样提高程序性能。3理解如何创建、管理和销毁线程。4理解线程生命期。5理解线程同步。6理解线程优先级和调度。,多线程处理具有广泛的应用。例如,当进程从网上下载音频或视频大文件时,不希望等整个文件下载完毕后才开始播放。为了解决这个问题,可以设计两个线程。一个线程下载文件,另一个线程负责播放。使得两种操作能同时进行。为避免播放操作的时断时续,还应对两个线程进行“同步”,在下载了足够的数据后才开始播放。与此同时,下载数据仍在继续。C+/CLI 的自动化“垃圾回收”也是通过多线程实现的。即程序运行的同时,自动化“垃
2、圾回收”线程始终跟踪着指针所引用的托管类对象的活动,并在其不再使用时,回收该对象所占用的内存资源。,.NET Framework 类库允许开发者轻松地使用并发性指令,即可以指定一个程序包含多个执行线程,每个线程都代表程序的一个独立运行的流程,程序的各个流程都可以并发执行。,16.1线程状态:线程生命期对于托管类多线程应用程序,Thread 和 Monitor 类型是尤为重要的。这两个类均在 System:Threading 命名空间中,Thread 用于线程的创建、运行和辅助功能,Monitor 用于创建线程间的同步机制。线程一旦成功创建,在任何时刻都处于某种“线程状态”。线程的状态包括:un
3、Satarted:“未启动”状态Running:“运行”状态Stopped:“停止”状态Blocked:“阻塞”状态WaitSleepJoin:“等待-休眠-联接”状态Suspended:“挂起”状态或“延迟执行”状态,线程的生命期(各种线程状态之间的转换)如下图所示:,Unstarted,Running,WaitSleepJoin,Suspended,Stopped,Blocked,Resume,Wait,Sleep,Join,Pulse,PulseAll,Interrupt,被依赖线程终止,Suspend,Abort,线程完成,同步锁禁用,发出I/O请求,同步锁可用,I/O操作完成,Sta
4、rt,1Unstarted-Running:程序创建一个线程对象,并向构造函数传递一个 ThreadStart 委托时,线程将从 Unstarted 开始其生命期。ThreadStart 委托指定了线程在生命期内要进行的操作。该委托必须是一个不接收任何参数的 void 类型方法。在没有调用 Thread 对象的 Start 方法之前,线程始终处于 Unstarted 状态。一旦方法 Start 方法被调用,线程便进入 Running 状态。2 Running-Stopped:发生这种状态转换的情况有两种:线程在其委托 ThreadStart 终止后,便进入 Stopped 状态。Thread
5、对象的 Abort 方法被调用,线程抛出一个线程异常 ThreadAbortException,导致线程终止。如果线程已经处于 Stopped 状态,并且程序中不再存在指向该线程的指针,,则垃圾回收器就能从内存中撤消该线程对象。注意,从内部来说,调用线程的 Abort 方法时,线程实际 上先进入并保持“AbortRequested”状态,直至接收到线程 异常 ThreadAbortException,线程才进入 Stopped 状态。因 此,调用 Abort 方法时,如果线程处于 WaitSleepJoin、Suspended 或者 Blocked 状态,线程就会暂时保持当前状态 以及“Abo
6、rtRequested”状态,直到线程离开了当前状态,才 能接收到 ThreadAbortException。3Running Blocked:发生这种状态转换的情况有两种:线程请求 I/O 等操作,使得该线程无法使用处理器(即使 处理器可用),便进入 Blocked 状态,即操作系统会一直,阻塞线程的执行,直到线程请求的 I/O 等操作完成后,线 程将恢复 Running 状态。线程同步时,必须调用 Monitor 的 Enter 方法,以便获得一 个对象的同步锁,如果该锁暂时被禁用(其他同步线程占 用),则该线程就会进入 Blocked 状态;直到同步锁可用 后,该线程才恢复 Runnin
7、g 状态。4Running WaitSleepJoin:发生这种状态转换的情况有三种:线程遇到了暂时不能执行的代码(通常是因为不满足某个 执行条件),线程就可能调用 Monitor 的 Wait 方法,从而 进入 WaitSleepJoin 状态。之后,只有另一个线程调用了 Monitor 的 Pulse 或 PulseAll 方法,线程恢复 Running 状态。,一个 Running 状态线程的 Sleep 方法可被调用,使线程进 入 WaitSleepJoin 状态。休眠时间由传给 Sleep 方法的参数 指定。一旦休眠时间到期,该线程便恢复 Running 状态。对于调用 Monito
8、r 类型的 Wait 方法或 Thread 类型的 Sleep 方 法进入休眠状态的线程,只要正在休眠线程的 Interrupt 方 法被调用,就会使休眠线程离开 WaitSleepJoin 状态,恢复 Running状态 如果一个线程应在另一线程终止后才能继续执行,则称此 线程为依赖线程。依赖线程需要调用另一线程的 Join 方 法来“联接”这两个线程。两个线程“联接”后,依赖线程便 进入 WaitSleepJoin 状态。直到另一个线程结束,依赖线程 便脱离 WaitSleepJoin 状态,进入 Running 状态。,5Running Suspended:一个 Running 状态线程
9、的 Suspend 方法被线程自身或另一线程调用之后,该线程就会被挂起,进入 Suspended 状态。只有被挂起线程的 Resume 方法被调用,被挂起的线程才会恢复 Running 状态。在内部,调用 Running 状态线程的 Suspend 方法后,线程首先进入“SuspendRequested”状态,再进入 Suspended 状态。在等待 Suspend 请求时,线程将保持“SuspendRequested”状态。因此,调用线程的 Suspend 方法时,如果线程正处于 WaitSleepJoin 或 Blocked 状态,则线程将保持它当前的状态;直到线程离开了当前状态,才会响应
10、 Suspend 请求。,6如果线程的属性 IsBackground 被设置为 true,则该线程就会保持 Background 状态。线程可以同时处于 Background 状态和其他任何一种状态。结束一个进程的条件之一是必须等待所有 Foreground 线程(不处于 Background 状态的线程)结束执行,或进入 Stopped 状态,否则进程将不能终止。但是如果一个进程中只剩下 Background 线程,那么 CLR 可调用那些 Background 线程的 Abort 方法来终止它们,并使进程终止。,16.2线程优先级和线程调度每个线程都有优先级,优先的取值范围是由定义在命名空
11、间 System:Threading 中的枚举 ThreadPriority 确定的。该枚举的成员包括 Lowest、BelowNormal、Normal、AboveNormal 和 Highest。每个线程的缺省优先级是 Normal。Windows 平台支持“时间分片”的概念,它使优先级相同的线程能共享一个处理器。这就是说,系统为优先级相同的线程每次分配一段相同长度的短暂的处理器时间,称为一个“quantum”(量子时间)或者“time slice”(时间片)。,线程只能在分配给自己的量子时间中才能执行。量子时间到期后,即使线程尚未结束执行,处理器也会离开那个线程,改为执行下一个具有相同优
12、先级的线程。线程调度器的职责是保证当前运行的总是具有最高优先级的线程;如果有多个相同优先级的线程,就保证这些线程“轮流”执行固定的量子时间。只有相同优先级的线程全部执行结束,具有较低优先级的线程才能得到用于执行的量子时间。下图描述了优先级队列对线程执行的作用:,优先级 Highest,优先级 AboveNormal,优先级 Normal,优先级 BelowNormal,优先级 Lowest,A,B,就绪线程,C,D,E,F,G,注意,高优先级的线程可能推迟低优先级的线程的执行,而且推迟时间可能是不确定的,这种不确定的推迟被称为“饥饿”(Starvation)。在 Windows 中,线程调度器
13、会随着时间的推移,增大一个“饥饿”线程的优先级,使该线程能够执行一个量子时间。一旦该量子时间到期(而且线程还没有终止),线程又会恢复它的原有优先级。如果线程继续遭受不确定的推迟,线程调度器可能会重复上述操作。线程会一直执行,直到遇到以下几种情况才会暂停或停止:线程死亡状态;线程因输入/输出(或其他原因)而进入阻塞状态;,线程的 Sleep 方法、Join 方法或者 Monitor 类型的 Wait 方法被调用,从而进入等待状态;线程被优先级更高的线程占先执行而进入暂停状态;分配给线程的量子时间到期后进入的暂停或停止执行状态。暂停执行的线程,一旦遇到以下情况就回恢复执行:休眠的线程被唤醒;正被阻
14、塞的线程的输入/输出操作结束;为处于等待状态的线程调用了 Monitor 类型的 Pulse 或 PulseAll 方法;,当一个联接线程结束时,如果有一个优先级比当前正要执行的依赖线程高的线程处于暂停状态,该线程就会抢先执行;否则依赖线程就会执行。接下来我们将通过对一系列的简单多线程应用程序实例的分析,掌握托管类多线程应用程序的设计、编程基础。每一个实例,我们都只对程序的设计目的、设计思路、编程资源、编程要点和运行结果进行分析。读者可以根据这些分析对程序代码进行详细学习,在理解的基础上自行重复实例的设计和实现,并提倡在模仿的基础上添加自己的设计意图和需求。,16.3创建和执行线程 Simpl
15、eThreads1设计目的演示基本的线程处理技术,如何构造一个 Thread 对象,以及如何使用 Thread 类的静态方法 Sleep。,2设计思路根据设计目的,在程序中创建 3 个具有缺省优先级 Normal 的线程。每个线程开始执行后都显示一条信息:“表明它将要进入休眠过程(休眠时间为 0 5000 毫秒范围内的随机数),然后立即进入休眠状态。”每个线程被唤醒(休眠过程结束)时,都显示一条信息:“显示线程的名称,并表明该线程已经结束休眠,并将要进入 Stopped 状态。”本例是一个托管控制台程序。,3编程资源创建线程对象的 Thread 类是在 System:Threading 命名空
16、间中定义的。该类的常用属性和方法如下:常用属性:CurrentThread静态属性,获取当前正在执行的线程对象的引用 Thread。IsBackground动态属性,获取和设置线程的 Background(后台)属性。该属性为 true,指示线程为后台线程;否则为前台线程。Name动态属性,获取和设置线程名称。,Priority动态属性,获取和设置线程的优先级。常用方法:Abort动态方法,终止线程。Join动态方法,阻塞线程,直到一个线程终止执行。Sleep静态方法,阻塞线程,直到指定时间(毫秒)结束。Start动态方法,启动线程按预定流程执行。ToString动态方法,返回一个描述线程的字
17、符串。,4编程要点 由于程序中使用了线程类 Thread,因此需要在程序中使用该类源代码文件中添加使用 Thread 类所属的命名空间语句:using namespace System:Threading;本例中的三个线程对象的创建和启动都是在主函数 main 中完成的,而线程的操作是由自定义类 MessagePrinter 的 Print 方法提供的。MessagePrinter 和 MessagePrinter:Print 的定义如下:public ref class MessagePrinterpublic:MessagePrinter(void);void Print(void);,p
18、rivate:int sleepTime;static Random random=gcnew Random();/end class MessagePrintervoid MessagePrinter:Print()/obtain pointer to currently executing threadThread current=Thread:CurrentThread;/put thread to sleep for sleepTime amount of timeConsole:WriteLine(String:Concat(current-Name,L going to sleep
19、 for,sleepTime.ToString();Thread:Sleep(sleepTime);,/print thread nameConsole:WriteLine(L0 done sleeping,current-Name);/end method Print其中线程的休眠时间 sleepTime 是在 MessagePrinter 类对象创建时,由构造函数确定的:MessagePrinter:MessagePrinter/pick random sleep time between 0 and 5 secondssleepTime=random-Next(5001);,程序的主函数
20、 main 的关键代码如下:int main(array args)/Create and name each thread.Use MessagePrinters/Print method as argument to ThreadStart delegateMessagePrinter printer1=gcnew MessagePrinter();Thread thread1=gcnew Thread(gcnew ThreadStart(printer1,MessagePrinter printer3=gcnew MessagePrinter();Thread thread3=gcnew
21、 Thread(gcnew ThreadStart(printer3,/end main,5运行结果,16.4无线程同步的生产者/消费者关系 UnSynchronized1设计目的在“生产者/消费者”(Producer/Consumer)关系中,应用程序的“生产者”部分生成数据,“消费者”部分则使用数据。在多线程的“生产者/消费者”关系中,“生产者线程”调用一个“生产方法”来生产数据,并将数据放到名为“缓冲区”的共享内存区域。“消费者线程”则调用一个“消费方法”,从缓冲区中读取数据。如果正在等待放入下一批数据的生产者线程发现消费者线程尚未从缓冲区读取上一批数据,生产者线程应该调用 Wait 方
22、法;否则,消费者线程将无法看到上一批数据,导致应用程序,丢失那些数据。消费者线程读取了数据后,应调用 Pulse 方法,使正在等待的生产者继续。如果消费者线程发现缓冲区为空,或者判断出缓冲区中的数据已被它读取过,消费者就应该调用 Wait 方法;否则,消费者线程就会从缓冲区读入“垃圾”数据,或者重复处理以前的数据。生产者线程将下一批数据放入缓冲区后,生产者应调用 Pulse 方法,使消费者线程继续。上述的生产和消费过程是在生产者线程和消费者线程对共享数据进行同步访问下完成的。如果不进行同步就会生产逻辑错误。设计本例的目的就是要演示:由于不同步产生的逻辑错误是怎样发生的。这样的逻辑错误会引起什么
23、样的后果。,2设计思路根据设计目的,我们将本例设计、编写成一个多线程实现共享数据的无同步生产和消费的应用程序。为此,程序中包含三个类:HoldIntegerUnsynchronized提供共享数据缓冲区,和对缓冲区的基本读写方法。Producer提供“生产线程”的“生产方法”和执行方法的必要数据。Consumer提供“消费线程”的“消费方法”和执行方法的必要数据。本例也是一个托管控制台程序。,3编程资源同前节(略)4编程要点 由于程序中使用了线程类 Thread,因此需要在程序中使用该类源代码文件中添加使用 Thread 类所属的命名空间语句:using namespace System:Th
24、reading;在 HoldIntegerUnsynchronized 类中,包含一个私有数据成员:private:int buffer;/作为共享数据的缓冲区对缓冲区的读写操作是通过可访问属性 Buffer 向外提供的:property int Bufferint get(),Console:WriteLine(L0 reads 1,Thread:CurrentThread-Name,buffer.ToString();return buffer;void set(int value)Console:WriteLine(L0 writes 1,Thread:CurrentThread-Nam
25、e,value.ToString();buffer=value;,在 Producer 类的数据成员包含“生产方法”执行时必需的共享数据对象和用于生产延时的随机休眠时间:private:HoldIntegerUnsynchronizedsharedLocation;RandomrandomSleepTime;这两个数据成员在 Producer 类对象创建时,进行初始化。Producer:Producer(HoldIntegerUnsynchronized shared,Random random)sharedLocation=shared;randomSleepTime=random;,“生产
26、方法”的操作定义如下:void Producer:Produce()/sleep for random intreval up to 3000 milliseconds/then set sharedLocations Buffer propertyfor(int count=1;count Next(1,3000);sharedLocation-Buffer=count;/end forConsole:WriteLine(L0 done producing.nTerminating 0,Thread:CurrentThread-Name);/end method Produce,在 Cons
27、umer 类的数据成员包含“消费方法”执行执行时必需的共享数据对象和 用于消费延时的随机休眠时间:private:HoldIntegerUnsynchronizedsharedLocation;RandomrandomSleepTime;这两个数据成员在 Consumer 类对象创建时,进行初始化。Consumer:Consumer(HoldIntegerUnsynchronized shared,Random random)sharedLocation=shared;randomSleepTime=random;,“消费方法”的操作定义如下:void Consumer:Consume()in
28、t sum=0;/sleep for random intreval up to 3000 milliseconds/then add sharedLocations Buffer property value to sumfor(int count=1;count Next(1,3000);sum+=sharedLocation-Buffer;/end forConsole:WriteLine(L0 read values totaling:1.nTerminating 0,Thread:CurrentThread-Name,sum.ToString();/end method Consum
29、e,“生产线程”和“消费线程”的创建与启动是在主函数 main 中完成的。在两个线程创建之前,必须先建共享缓冲区、生产者和消费者对象。例如“生产线程”对象的创建和启动:HoldIntegerUnsynchronized holdInteger=gcnew HoldIntegerUnsynchronized();Random random=gcnew Random();Producer producer=gcnew Producer(holdInteger,random);Thread producerThread=gcnew Thread(gcnew ThreadStart(producer,
30、5运行结果,分析一次执行结果:消费者读入了一个并非生产者生产的错误数据-1;数据 1、2、3 顺序被生产者生产,并正确被消费者读入消 费;消费者线程结束执行时,发现消费的数据总值不等于生产 者生产的数据总值,因为第一次读入的数据是错误的;数据 4 未被消费,因为已经没有数据的消费者。注意,如果多次执行,则会发现每次运行的结果都可能是不一样。,16.5有线程同步的生产者/消费者关系 Synchronized1设计目的线程之间的同步是调用 Monitor 类的几个静态方法:Enter、Wait、Pulse/PulseAll 和 Exit 相互配合实现的。本例的设计目的是学会在设计和编写多线程应用程
31、序时,如何正确地加入同步操作,实现不同线程访问共享数据时,能避免逻辑错误。,2设计思路根据设计目的,我们在实例 UnSynchronized 的基础上增加对共享缓冲区访问的线程同步操作,使得实例 UnSynchronized 运行时出现的逻辑错误和不可预测性得以避免。为此,需要在共享缓冲区类 HoldIntegerSynchronized 的属性接口中对缓冲区 buffer 的读写操作中增加线程同步控制。为了更好地描述线程执行期间,线程的操作细节和共享缓冲区的状态变化,需要为 HoldIntegerSynchronized 类添加一个状态显示方法。由于需要在主函数 main 中调用 HoldI
32、ntegerSynchronized 的状态显示方法,因此主函数 main 需要增加少许相应的新代码。而 Producer 和 Consumer 类无须进行任何修改。,3编程资源前面谈及的 Monitor 类的几个静态方法的作用如下:Enter获取对指定线程对象的独占锁,被指定的线程对象由参数传递。Wait释放指定线程独占的锁,阻塞该线程,直至再次获取该线程的独占锁。被指定的线程对象由参数传递。Pulse唤醒正处于被阻塞的线程队列中的一个线程继续执行。,PulseAll唤醒正处于被阻塞的线程队列中的所有线程继续执行。Exit释放对指定线程对象的独占锁,被指定的线程对象由参数传递。,4编程要点
33、由于程序中使用了线程类 Thread,因此需要在程序中使用该类源代码文件中添加使用 Thread 类所属的命名空间语句:using namespace System:Threading;增加对缓冲区访问的同步控制,HoldIntegerSynchronized 类中应该添加一个数据成员:int occupiedBufferCount;用于指示缓冲区占用状况。共享缓冲区 buffer 的对外可访问属性 Buffer 的定义应该被修改如下:,property int Bufferint get()Monitor:Enter(this);if(occupiedBufferCount=0)Consol
34、e:WriteLine(L0 tries to read.,Thread:CurrentThread-Name);DisplayState(String:Concat(LBuffer empty.,Thread:CurrentThread-Name,L waits.);Monitor:Wait(this);/end if,occupiedBufferCount-;DisplayState(String:Concat(Thread:CurrentThread-Name,L reads,buffer.ToString();Monitor:Pulse(this);int bufferCopy=buf
35、fer;Monitor:Exit(this);return bufferCopy;void set(int value)Monitor:Enter(this);if(occupiedBufferCount=1),Console:WriteLine(L0 tries to write.,Thread:CurrentThread-Name);DisplayState(String:Concat(LBuffer full.,Thread:CurrentThread-Name,L waits.);Monitor:Wait(this);/end if HoldIntegerSynchronized:bu
36、ffer=value;occupiedBufferCount+;DisplayState(String:Concat(Thread:CurrentThread-Name,L writes,buffer.ToString();Monitor:Pulse(this);Monitor:Exit(this);,添加状态显示函数 DisplayState,其操作代码定义如下:void HoldIntegerSynchronized:DisplayState(String operation)Console:WriteLine(L0,-35 1,-9 2n,operation,buffer.ToStrin
37、g(),occupiedBufferCount.ToString();/end method DisplayState occupiedBufferCount注意,格式串 L“0,-35 1,-9 2n”表示第一个数据 operation 的显示域为 35 个字符宽度;第二数据 buffer 的显示域为 9 个字符宽度;第三个数据 occupiedBufferCount 的显示域不定宽度。,在主函数 main 中需要增加如下代码:int main(array args)/create shared object used by threadsHoldIntegerSynchronized ho
38、ldInteger=gcnew HoldIntegerSynchronized();/Random object used by each threadRandom random=gcnew Random();/create Producer and Consumer objectsProducer producer=gcnew Producer(holdInteger,random);Consumer consumer=gcnew Consumer(holdInteger,random);/output column heads and initial buffer stateConsole
39、:WriteLine(L0,-351,-92n,LOperation,LBuffer,LOccupied Count“);,holdInteger-DisplayState(LInitial state“);/create threads for producer and consumer and set/delegates for each threadThread producerThread=gcnew Thread(gcnew ThreadStart(producer,/end main,5运行结果,16.6生产者/消费者关系:循环缓冲区 CircularBuffer1设计目的分析实例
40、 Synchronized 的运行结果,发现该程序虽然实现对共享数据生产和消费的同步,避免了丢失数据、使用垃圾数据等逻辑错误;但线程过多地处于等待状态,使得运行效率不高。引起线程过多地处于等待状态的直接原因是线程执行的速度不一致,无论的生产快于消费,还是消费快于生产,都会引起较快的线程过多地处于等待状态。即使线程开始以相同的速度执行,但运行一段时间之后,线程之间的相对执行速度也可能失去“同步”。,由于操作系统、网络用户和其他组件之间存在着太多的交互,这些原因最终会使线程工作于不同的速度。所以我们无法保证异步并发线程的相对速度。多个线程共享资源并以基本相同的速度工作时,为了尽可能缩短等待时间,可
41、以使用一个循环缓冲区(Circular Buffer),它提供了额外的缓冲区,生产者可在其中放入值,而消费者可从中获取那些值。假定缓冲区是一个数组,生产者和消费者从数组头开始工作,任何线程一旦抵达数组尾,就回到数组的第一个元素,并执行下一任务。循环往复使用共享缓冲区。,注意,假如生产者和消费者以不同速度运行,就可能不适合使用循环缓冲区。例如消费者总是比生产者快,那么只包含一个数据内存空间的缓冲区就足够了,更多的内存纯属浪费。反之如果生产者快,缓冲区就可能需要包含不限数量的数据内存空间,以便存放不断生产的数据。使用循环缓冲区的关键在于足够的、恰当的数量的额外单元,以处理预料之中的“额外”生成的数
42、据。例如,我们发现生产者总是生成比消费者的使用能力多 3 倍的数据,就可以定义至少包含 3 个单元的缓冲区,以处理额外生成的数据。缓冲区不能太小,否则线程会等待更长的时间。相反,也不能太大,否则会浪费内存。,本例的设计目的是体会循环缓冲区对提高多线程应用程序的运行效率的作用,并初步学会为多线程应用程序设计合理的循环缓冲区。,2设计思路根据设计目的,我们在前一节 Synchronized 实例的基础上,将缓冲区修改为由数组实现的循环缓冲区。为了更友好地为用户提供程序运行期间,线程执行的状态信息,本例将设计为一个 Windows GUI 程序。为此,需要对 HoldIntegerSynchroni
43、zed、Producer 和 Consumer 类进行相应的修改。HoldIntegerSynchronized 类需要包含五个数据成员:array buffer;/共享循环缓冲区int occupiedBufferCount;/被占用的缓冲区计数,int readLocation;/缓冲区的读取位置指针int writeLocation;/缓冲区的写入位置指针TextBox outputTextBox;/输出显示文本框控件HoldIntegerSynchronized 类的属性接口 Buffer 的操作逻辑无须变化,而只需要将信息文本的显示操作从控制台方式转到窗口控件方式。另外,为了适应窗口
44、程序的需要,定义一个产生操作状态输出信息的方法 CreateStateOutput 替代实例 Synchronized 中定义的方法 DisplayState。,对于 Producer 和 Consumer 类无须进行许多修改,而只需要增加一个输出文本框类 TextBox 的数据成员 outputTextBox 以及在生产方法 Produce 和消费方法 Consume 中修改信息文本输出的方式,以适应窗口应用程序的需要。在实例 Synchronized 中,线程的创建和启动是在主函数 main中完成的。而在一个窗口应用程序中,线程的创建和启动就应该在程序主窗体创建时完成。,3编程资源同前节(
45、略)。,4编程要点 在 HoldIntegerSynchronized 类的头文件中需要添加如下使用某些命名空间语句:using namespace System;using namespace System:Windows:Forms;using namespace System:Threading;以便满足该类定义中使用系统中预定义类的需要。在 HoldIntegerSynchronized 类的构造函数中必须完成所有类数据成员的创建和初始化:HoldIntegerSynchronized:HoldIntegerSynchronized(TextBox output):occupiedBu
46、fferCount(0),readLocation(0),writeLocation(0),buffers=gcnew array(3);for(int i=0;i Length;i+)buffersi=-1;outputTextBox=output;输出信息产生方法 CreateStateOutput 的操作定义:String HoldIntegerSynchronized:CreateStateOutput()/display first line of state informationString output=String:Concat(L(buffers occupied:,occ
47、upiedBufferCount.ToString(),L)rnbuffers:);,for(int i=0;i Length;i+)output=String:Concat(output,L,buffersi.ToString(),L);output=String:Concat(output,Lrn);/display second line of state informationoutput=String:Concat(output,L);for(int i=0;i Length;i+)output=String:Concat(output,L-);output=String:Conca
48、t(output,Lrn);/display third line of state informationoutput=String:Concat(output,L);/display readLocation(R)and writeLocation(W)/indicators below appropriate buffer locations,for(int i=0;i Length;i+)if(i=writeLocation)/end method CreateStateOutput,程序的主窗体 Form1 的创建是执行 main 函数中的语句:Application:Run(gcn
49、ew Form1();完成的。为了在 Form1 的创建时添加线程的创建和启动操作,就要在 Form1 类定义中添加 Form1 的 Load 事件处理方法,并在该方法中完成上述操作。Form1 的 Load 事件处理方法 Form1_Load 的操作定义如下:System:Void Form1_Load(System:Object sender,System:EventArgs e)/create shared object HoldIntegerSynchronized sharedLocation=gcnew HoldIntegerSynchronized(outputTextBox);
50、,/display sharedLocation state before producer/and consumer threads begin execution outputTextBox-Text=sharedLocation-CreateStateOutput();/Random object used by each thread Random random=gcnew Random();/create Producer and Consumer objects Producer producer=gcnew Producer(sharedLocation,random,outpu