第七章 多线程程序设计

43
1 第第第 第第第第第第第 7.1 第第第第 7.2 第第第第第第 7.3 第第第第第第第第第第第第第第

Upload: ivo

Post on 19-Jan-2016

97 views

Category:

Documents


1 download

DESCRIPTION

第七章 多线程程序设计. 7.1 创建线程 7.2 多个线程互斥 7.3 生产者线程和消费者线程的同步. - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: 第七章   多线程程序设计

1

第七章 多线程程序设计

7.1 创建线程 7.2 多个线程互斥 7.3 生产者线程和消费者线程的同步

Page 2: 第七章   多线程程序设计

2如果在一个程序中,有多个工作要同时做,可以采

用多线程。在 Windows 操作系统中可以运行多个程序,把一个运行的程序叫做一个进程。一个进程又可以有多个线程,每个线程轮流占用 CPU 的运行时间, Windows 操作系统将时间分为时间片,一个线程用完一个时间片后,操作系统将此线程挂起,将另一个线程唤醒,使其使用下一个时间片,操作系统不断的把线程挂起,唤醒,再挂起,再唤醒,如此反复,由于现在 CPU 的速度比较快,给人的感觉象是多个线程同时执行。 Windows 操作系统中有很多这样的例子,例如复制文件时,一方面在进行磁盘的读写操作,同时一张纸不停的从一个文件夹飘到另一个文件夹,这个飘的动作实际上是一段动画,两个动作是在不同线程中完成的,就像两个动作是同时进行的。又如 Word 程序中的拼写检查也是在另一个线程中完成的。每个进程最少有一个线程,叫主线程,是进程自动创建的,每进程可以创建多个线程。

Page 3: 第七章   多线程程序设计

3

Process 类Process 类

Process 类位于 System.Diagnostics 名称空间下,它专门用于完成系统进程的管理任务。

可以在本地计算机上启动和停止进程,也可以向进程查询特定类型的信息。在远程计算机上,无法启动和停止进程,但可以查询进程的相关信息。在对进程进行操作时,首先要创建 Process 类的实例,其次还需要设置其对象成员的 StartInfo 属性,最后调用它的 Start 方法。

Page 4: 第七章   多线程程序设计

4

例 . 启动、停止和观察进程1. 新建一个名为 ProcessExample 的 Windows 应用程序。2. 从工具箱中将 Process 组件拖放到设计窗体。3. 添加名称空间 :

using System.Diagnostics;

using System.Threading;4. 添加“启动记事本”、“停止记事本”和“观察所有进程”

三个按钮,并添加 Click 事件代码: private void buttonStart_Click(object sender, EventArgs e)

{

process1.StartInfo.FileName = "notepad.exe";

// 启动 Notepad.exe 进程 .

process1.Start();

}

Page 5: 第七章   多线程程序设计

5private void buttonStop_Click(object sender, EventArgs e)

{

// 创建新的 Process 组件的数组 , 并将它们与指定的进程名称( Notepad )的所有进程资源相关联 .

Process[] myprocesses;

myprocesses = Process.GetProcessesByName("Notepad");

foreach (Process instance in myprocesses)

{

// 设置终止当前线程前等待 1000 毫秒 instance.WaitForExit(1000);

instance.CloseMainWindow();

}

}

private void buttonView_Click(object sender, EventArgs e)

{

listBox1.Items.Clear();

// 创建 Process 类型的数组 , 并将它们与系统内所有进程相关联

Page 6: 第七章   多线程程序设计

6Process[] processes; processes = Process.GetProcesses(); foreach (Process p in processes) { // 由于访问 Idle 的 StartTime 会出现异常,所以

将其排除在外 if (p.ProcessName != "Idle") { // 将每个进程名和进程开始时间加入 listBox1

中 this.listBox1.Items.Add( string.Format("{0,-30}{1:h:m:s}",

p.ProcessName, p.StartTime)); } }}

Page 7: 第七章   多线程程序设计

7

超级链接到微软网站 :

System.Diagnostics.Process.Start("http://www.micosoft.com.cn"

打开一个窗口,列出 C 盘根目录下的文件及文件夹 :

System.Diagnostics.Process.Start("C:/");

Page 8: 第七章   多线程程序设计

8

7.1 创建线程

本节介绍线程类 (Thread) 的属性和方法以及如何创建线程。

Page 9: 第七章   多线程程序设计

9

7.1.1 线程类 (Thread) 的属性和方法 线程类在命名空间 System.Threading 中定义

的,因此如果要创建多线程,必须引入命名空间System.Threading 。 Thread 类的常用属性和方法如下:

• 属性 Priority :设置线程优先级,有 5 种优先级类别: AboveNormal( 稍高 ) 、 BelowNormal( 稍低 ) 、 Normal( 中等,默认值 ) 、 Highest( 最高 ) 和 Lowest( 最低 ) 。例如语句myThread.Priority=ThreadPriority.Highest 设置线程 myThread 的优先级为最高。优先级高的线程先运行,只有优先级高的线程停止、休眠或暂停时,低优先级的线程才能运行。

Page 10: 第七章   多线程程序设计

10

• 构造函数: Thread(new ThreadStart( 线程中要执行的无参数方法名 )) ,参数中指定的方法需要程序员自己定义,这个方法完成线程所要完成的任务,退出该方法,线程结束。该方法必须为公有void 类型的方法,无参数。如果希望有参数,可使用 C#2.0 中新构造函数: Thread(new ParameterizedThreadStart( 线程中要执行的有参数方法名 )) 。

• 方法 Start() :建立线程类对象后,线程处于未启动状态,这个方法使线程改变为就绪状态,如果能获的 CPU 的运行时间,线程变为运行状态。

• 方法 IsAlive() :判断线程对象是否存在, =true ,线程存在。

Page 11: 第七章   多线程程序设计

11• 方法 Abort() :撤销线程对象。不能撤销一个已

不存在的线程对象,因此在撤销一个线程对象前,必须用方法 IsAlive()判断线程对象是否存在。

• 静态方法 Sleep() :线程休眠参数设定的时间,单位为毫秒,此时线程处于休眠状态。线程休眠后,允许其他就绪线程运行。休眠指定时间后,线程变为就绪状态。

• 方法 Suspend() 和 Resume() : Suspend()方法使线程变为挂起状态。 Resume 方法使挂起线程变为就绪状态,如能获的 CPU 的运行时间,线程变为运行状态。如线程多次被挂起,调用一次Resume() 方法就可以把线程唤醒。由于不安全C#2.0 建议不使用这两个函数。

Page 12: 第七章   多线程程序设计

12

7.1.2 创建线程 【例 7.1】本例使用线程类 Thread 创建一个新的线

程,在标签控件中显示该线程运行的时间。在窗体放置 2 个按钮,单击按钮完成新建和停止线程的功能。

1. 新建项目。在窗体中放置 2 个按钮和 1 个标签控件(label1) 。 button1 的属性 Text=“ 新线程” , Enabled=true 。 button2 的属性 Text=“撤销” , Enabled=false 。

2. 在 Form1.cs头部增加语句:using System.Threading。

3. 为 Form1类中声明一个代表类 dFun、定义一个类 dFun的变量和线程类变量:

Page 13: 第七章   多线程程序设计

13delegate void dFun(string text);dFun dFun1; //dFun 类变量private Thread thread; // 线程类变量4. 为标题为“新线程”的按钮 (button1)增加单击事件处理函数如下:

private void button1_Click(object sender, EventArgs e)

{ thread=new Thread(new ThreadStart(fun)); label1.Text="0"; //运行时间从 0 开始

thread.Start(); button1.Enabled=false; button2.Enabled=true;}

Page 14: 第七章   多线程程序设计

14

5. 为标题为“撤销”的按钮 (button2)增加单击事件处理函数如下:

private void button2_Click(object sender, EventArgs e)

{ if(thread.IsAlive) { thread.Abort(); //撤销线程对象 button1.Enabled=true; button2.Enabled=false; }}

Page 15: 第七章   多线程程序设计

156. C# 线程模型允许将任何一个 void 类型

的公有方法(静态或非静态)作为线程方法,因此允许在任何一个类(不要求这个类是某个类的子类)中定义线程方法,而且同一个类中可以定义多个线程方法。 C#2.0 不允许在此函数中直接修改线程外控件属性,这是防止多个线程同时修改同一控件的同一属性发生错误,必须使用控件的 Invoke 方法修改线程外控件属性, Invoke 方法有两个参数,参数 1是修改控件属性的方法的代表 , 参数 2 是object 数组,是传递给参数 1 代表的方法的参数。为 Form1 类定义一个线程方法如下:

Page 16: 第七章   多线程程序设计

16

public void fun(){ while(true)//退出该方法,线程结束 { int x=Convert.ToInt32(label1.Text); x++; string s=Convert.ToString(x); label1.Invoke(dFun1,new object[]{s});

Thread.Sleep(1000); //线程休眠 1 秒钟

}}7. 为 Form1 类定义一个修改 label1.Text 的方法如下:

Page 17: 第七章   多线程程序设计

17

private void SetText(string text){ label1.Text = text; } 8. 在 Form1 类的构造函数的最后增加如下语句: dFun1=new dFun(SetText); 9. 在关闭程序之前,必须撤销线程对象。为主窗体

的 Closing 事件增加事件处理函数如下: private void Form1_FormClosing(object

sender, FormClosingEventArgs e) { if(thread.IsAlive) thread.Abort(); } 10.编译运行,单击标题为 "新线程 "的按钮,新线

程开始,计数器从 0 开始计数。单击标题为 "撤销 "的按钮,线程对象被撤销,计数器停止计数。

Page 18: 第七章   多线程程序设计

18

7.1.3 进度条 (ProgressBar)控件 进度条 (ProgressBar)控件经常用来显示一个任务的

进度。有时,要完成一个长时间的任务,例如一个软件的安装,如果没有任何提示,使用者可能分不清任务是在进行中,还是死机了,可以使用进度条显示安装进度,表示安装正在进行。进度条常用属性如下:

• 属性 Maximum 和 Minimum :进度条代表的最大值和最小值 ( 整数 ) ,默认值分别为 100 , 0 。

• 属性 Step :变化的步长,默认值为 10 。 • 属性 Value :进度条当前位置代表的值。修改该值,达到一个 Step ,进度增加一格。

Page 19: 第七章   多线程程序设计

19

7.1.3 用线程控制进度条 有时需要建立多个线程,每个线程要实现的功能基本相同,但有个别参数不同,例如,每个线程完成同样的任务,但控制的对象不同。可使用 C#2.0中定义的线程类构造函数 Thread(new ParameterizedThreadStart(…)) 创建新线程,…处为一个实现线程所要求任务的方法,该方法只允许有 1 个 object 类型参数,可用参数传递不同对象。实现方法见下例。【例 7.2】建立两个线程,分别控制两个进度条

(ProgressBar)控件,每个进度条的属性 Value变化的速率不一样。具体实现步骤如下,运行效果如图。

Page 20: 第七章   多线程程序设计

20

Page 21: 第七章   多线程程序设计

211. 新建项目。在 Form1.cs头部增加语句:

using System.Threading 。 2. 在窗体中放置 2 个进度条 (ProgressBar)控件。

属性 Name 分别为ProgressBar1 、 ProgressBar2 。

3. 为 Form1 类中声明一个结构、代表类 dFun 、定义一个类 dFun 的变量和线程类变量:

结构定义,由于要传递两个参数,定义结构代表两个参数

struct Fargs{ public ProgressBar PB;// 线程控制的对象 public int SleepT; } // 线程休眠时间

Page 22: 第七章   多线程程序设计

22delegate void dFun(ProgressBar p);dFun dFun1; //Fun 类变量private Thread thread1; // 线程类变量private Thread thread2;Fargs Frags1; //结构变量• 为 Form1 类定义一个线程方法如下 ( 注意只能有一个

object 类参数 ) : public void fun(object data) { Fargs Frags2 = (Fargs)data; ProgressBar p1 = Frags2.PB; int SleepTime = Frags2.SleepT; while (p1.Value <100) { p1.Invoke(dFun1, new object[] { p1 }); Thread.Sleep(SleepTime); } }5. 为 Form1 类定义一个修改进度条的属性 Value 的方法如下:

Page 23: 第七章   多线程程序设计

23private void SetValue(ProgressBar p2){ p2.Value += 1; }

6. 在 Form1 类的 Load 事件增加事件函数如下 (此处代码不能放在构造函数中 ):

private void Form1_Load(object sender, EventArgs e) { dFun1 = new dFun(SetValue); thread1 =

new Thread(new ParameterizedThreadStart(fun));

thread2 = new Thread(new

ParameterizedThreadStart(fun)); Frags1.PB = progressBar1; Frags1.SleepT = 100; thread1.Start(Frags1); //注意如何为 fun 方法传递参数 Frags1.PB = progressBar2; Frags1.SleepT = 200; thread2.Start(Frags1); }

Page 24: 第七章   多线程程序设计

24

7. 为主窗体的 Closing 事件增加事件处理函数如下:private void Form1_FormClosing(object

sender, FormClosingEventArgs e){ if(thread1.IsAlive) thread1.Abort();

if(thread2.IsAlive) thread2.Abort();}

8.编译,运行,可以看到两个进度条以不同的速度前进,当进度条被添满,线程停止。

Page 25: 第七章   多线程程序设计

25

7.1.5 BackgroundWorker 组件 Windows 应用程序如需要执行长时间的后台操作 ( 例如

Word 程序的拼写检查 ) ,可以建立一个优先级最低的线程完成这样的工作。编写这样线程代码可能是一项艰巨而又耗时的工作。在 VS2005 中,可以使用BackgroundWorker控件简化编程。控件常用属性和方法如下:

• 方法 RunWorkerAsync(object): 启动线程,参数将传递给线程 DoWork 事件处理函数。

• 方法 CancelAsync() :结束线程。 • 事件 DoWork :希望在线程中完成的工作代码放到此事

件处理函数中。 • 事件 RunWorkerCompleted :线程结束引发的事件。通过事件函数参数 2 获得退出的原因。

Page 26: 第七章   多线程程序设计

26

7.2 多个线程互斥 多个线程同时修改共享数据可能发生错误。假设 2 个

线程分别监视 2 个入口进入的人数,每当有人通过入口,线程用 C#语句对总人数变量执行加 1 操作。一条 C#语句可能包含若干机器语言语句,假设 C#语句加 1 操作包含的机器语言语句是:取总人数,加 1 ,再存回。操作系统可以在一条机器语言语句结束后,挂起运行的线程。如当前总人数为 5 ,线程 1 运行,监视到有人通过入口,取出总人数(=5) 后,线程 1 时间用完挂起。线程 2 唤醒,也监视到有人通过入口,并完成了总人数加 1 并送回的操作,总人数为6 ,线程 2 挂起。线程 1 唤醒,对已取出的总人数 ( 此时为5) 加 1 ,存回去,总人数应为 7 ,实为 6 ,少算一个。为了防止此类错误,在一个线程修改共享资源 ( 例如上例的总人数变量 ) 时,不允许其他线程对同一共享资源进行修改,这叫线程的互斥。这样的实例很多,例如计算机中的许多外设,网络中的打印机等都是共享资源,只允许一个进程或线程使用。

Page 27: 第七章   多线程程序设计

27

7.2.1 多个线程同时修改共享数据可能发生错误 【例 7.4】下边的例子模拟2 个线程同时修改同一个共享数据时可能发生的错误。

1. 新建项目。在 Form1.cs头部增加语句:using System.Threading 。

2. 为 Form1类定义 2 个线程类变量: Thread thread1,thread2。定义整形变量: int num=0。

3.在窗体中放置一个标签和按钮控件,按钮的事件处理函数如下: private void button1_Click(object sender,

EventArgs e) { label1.Text = num.ToString(); }4. 为 Form1类构造函数增加语句如下: thread1= new Thread(new ThreadStart(Fun1)); thread2= new Thread(new ThreadStart(Fun2)); thread1.Start(); thread2.Start();

Page 28: 第七章   多线程程序设计

285. 为 Form1 类中定义 Fun1() 和 Fun2() 方法如下: public void Fun1(){ int k,n;for(k=0;k<4;k++){ n=num;//取出 num ,可以把把 num想象为总人数 n++; // 加 1 //模拟复杂的费时运算,在此期间,有可能时间片用完 Thread.Sleep(20); num=n;//存回 num Thread.Sleep(50);}}//退出该方法,线程结束

Page 29: 第七章   多线程程序设计

29

public void Fun2(){ int k,n;for(k=0;k<4;k++){ n=num;

n++;Thread.Sleep(10);num=n;Thread.Sleep(100);

}}6.编译运行,单击按钮,标签控件应显示8 ,实际运

行多次,显示的数要小于 8 。

Page 30: 第七章   多线程程序设计

30

7.2.2 用 Lock语句实现互斥

Lock语句的形式如下: lock(e){ 访问共享资源的代码 } 。其中 e 指定要锁定的对象,锁定该对象内所有临界区,必须是引用类型,一般为this 。 Lock语句将访问共享资源的代码标记为临界区。临界区的意义是:假设线程 1 正在执行 e 对象的临界区中的代码时,如其他线程也要求执行这个 e 对象的任何临界区中代码,将被阻塞,一直到线程 1 退出临界区。【例 7.5】用 C#语句 Lock实现互斥。修改例 7.4中的 Fun1() 和 Fun2()方法如下:

Page 31: 第七章   多线程程序设计

31public void Fun1(){ int k,n;for(k=0;k<4;k++){ lock(this) // 这里的 this 是 Form1 类的对象 {n=num; // 这对大括号中代码为 this 的临界区 n++;//this 的临界区包含两部分,函数 Fun1 和 Fun2 中的临界区

Thread.Sleep(10); num=n; } Thread.Sleep(50);}} //退出该方法,线程结束

Page 32: 第七章   多线程程序设计

32public void Fun2(){ int k,n;for(k=0;k<4;k++){// 如有线程进入此临界区 ,其他线程就不能进入这个临界区

lock(this) {n=num; // 也不能进入前边的临界区 n++; Thread.Sleep(10); num=n; } Thread.Sleep(100);}}编译运行,单击按钮标签控件应显示 8 。如果有多个共享数据区,使用此方法不太方便。

Page 33: 第七章   多线程程序设计

33

7.2.3 用 Mutex 类实现互斥 可以使用 Mutex 类对象保护共享资源 ( 如上例中的总人数变量 ) 不被多个线程同时访问。 Mutex 类WaitOne 方法和 ReleaseMutex 方法之间代码是互斥体,这些代码要访问共享资源。 Mutex 类的WaitOne 方法分配互斥体访问权,该方法只向一个线程授予对互斥体的独占访问权。如果一个线程获取了互斥体,则要获取该互斥体的第二个线程将被挂起,直到第一个线程用 ReleaseMutex 方法释放该互斥体。【例 7.6】使用 Mutex 类对象实现互斥。修改例 7.4 ,

为 Form1 类增加私有 Mutex 类变量: private Mutex mut 。在 Form1 类构造函数中增加语句:mut=new Mutex();该句位置必须在建立线程语句之前。修改例 7.4 中的两个 Fun1() 和 Fun2()方法如下:

Page 34: 第七章   多线程程序设计

34public void Fun1(){ int k,n;for(k=0;k<4;k++){ mut.WaitOne(); // 等待互斥体访问权 n=num;// mut.WaitOne() 和 mut.ReleaseMutex()之间是互斥

体 n++;//Mutex 类对象 mut 的互斥体包含两部分,函数 Fun1 和 Fun2 中的互

斥体 Thread.Sleep(10);// 有线程进入一个互斥体,其他线程不能进入任何一个互斥

num=n; mut.ReleaseMutex(); //释放互斥体访问权 Thread.Sleep(50);}}//退出该方法,线程结束

Page 35: 第七章   多线程程序设计

35public void Fun2(){ int k,n;for(k=0;k<4;k++){ mut.WaitOne();

n=num;n++;Thread.Sleep(10);num=n;mut.ReleaseMutex();Thread.Sleep(100);

}}编译,运行,标签控件显示8 。如果有多个共享数据区,

可以定义多个 Mutex 类对象。

Page 36: 第七章   多线程程序设计

36

7.2.4 用 Monitor 类实现互斥 也可以使用 Monitor 类保护共享资源不被多个线程或

进程同时访问。 Monitor 类通过向单个线程授予对象锁来控制对对象的访问。只有拥有对象锁的线程才能执行临界区的代码,此时其他任何线程都不能获取该对象锁。只能使用 Monitor 类中的静态方法,不能创建 Monitor 类的实例。 Monitor 类中的静态方法主要有:

• 方法 Enter :获取参数指定对象的对象锁。此方法放在临界区的开头。如其他线程已获取对象锁,则该线程将被阻塞,直到其他线程释放对象锁,才能获取对象锁。

Page 37: 第七章   多线程程序设计

37• 方法 Wait :释放参数指定对象的对象锁,以便允许

其他被阻塞的线程获取对象锁。该线程进入等待状态,等待状态必须由其他线程用方法 Pulse 或PulseAll 唤醒,使等待状态线程变为就绪状态。

• 方法 Pulse 和 PulseAll :向等待线程队列中第一个或所有等待参数指定对象的对象锁的线程发送信息,占用对象锁的线程准备释放对象锁。执行方法 Exit后将释放对象锁。

• 方法 Exit :释放参数指定对象的对象锁。此操作还标记受对象锁保护的临界区的结尾。

• 使用 Monitor 类实现互斥也很简单,请读者修改例7_2_1 ,使用 Monitor 类实现互斥。 Monitor 类主要用来实现生产者和消费者关系中的线程的同步,具体例子见下一节。

Page 38: 第七章   多线程程序设计

38

7.3 生产者线程和消费者线程的同步 在生产者和消费者关系中,生产者线程产生数据,并把数据存到公共数据区,消费者线程使用数据,从公共数据区取出数据。显然如果公共数据区只能存一个数据,那么在消费者线程取出数据前,生产者线程不能放新数据到公共数据区,否则消费者线程将丢失数据。同样只有生产者线程把数据已经放到公共数据区,消费者线程才能取出数据,否则消费者线程不能取数据。这些就是所谓的生产者和消费者关系,必须要求生产者线程和消费者线程同步。

Page 39: 第七章   多线程程序设计

39

7.3.1 生产者线程和消费者线程不同步可能发生错误 【例 7.7】下边的例子模拟生产者线程和消费者线程不同步可能发生错误。有一个公共变量,要求生产者线程顺序放 1 到 4 到这个公共变量中,每放一个变量,消费者线程取出这个数求和,最后把和显示出来,显然和应为 10 。如不采取同步措施,和的结果不正确。

1. 新建项目。在 Form1.cs头部增加语句:using System.Threading 。

2. 为 Form1类定义 2 个线程类变量: Thread thread1,thread2。

3. 为 Form1类定义 2 个整形变量: int sum=0,x=-1。 4.在窗体中放置一个标签和按钮控件,按钮的事件处理函数如下:

private void button1_Click(object sender, EventArgs e)

{ label1.Text = sum.ToString(); }5. 为 Form1类构造函数增加语句如下:

Page 40: 第七章   多线程程序设计

40thread1= new Thread(new ThreadStart(Fun1));thread2= new Thread(new ThreadStart(Fun2));thread1.Start();thread2.Start();

6. 为 Form1 类定义 Fun1() 和 Fun2() 方法如下: public void Fun1() // 生产数据{ for(int k=1;k<5;k++){ x=k;Thread.Sleep(200); } }public void Fun2() // 消费数据{ for(int k=0;k<4;k++){ sum+=x;Thread.Sleep(100); } }7.编译运行,单击按钮,标签控件应显示 10 ,实际运行多次,显示的数不为 10 。

Page 41: 第七章   多线程程序设计

417.3.2 生产者线程和消费者线程同步的实现

修改上例,为 Form1 类定义 1 个布尔变量:bool mark=false 。其值为 false ,表示数据还未放到公共数据区 ( 即 x) 中,生产者线程可以放数据到公共数据区中,由于没有数据,消费线程不能取数据,必须等待。 mark=true ,表示数据已放到公共数据区 ( 即 x) 中,消费线程还未取数据,生产者线程不能再放数据到公共数据区中,必须等待。由于有了数据,消费线程可以取数据。修改 Fun1() 如下:

Page 42: 第七章   多线程程序设计

42public void Fun1() // 生产数据{ for(int k=1;k<5;k++){// 这里 this 是 Form1 类对象,得到 this 的对象锁 Monitor.Enter(this);//Monitor.Enter(this) 和 Monitor.Exit(this) 是临界区 if(mark)// 如消费者数据未取走,释放对象锁,生产者等待 Monitor.Wait(this); mark=!mark; x=k; Monitor.Pulse(this); //激活消费者线程 Monitor.Exit(this); }//释放 this 的对象锁 }}

Page 43: 第七章   多线程程序设计

43修改 Fun2() 如下:public void Fun2() // 消费数据{ for(int k=0;k<4;k++){ Monitor.Enter(this);if(!mark)Monitor.Wait(this);// 如果生产者未放数据,消费者等

待mark=!mark;sum+=x;Monitor.Pulse(this);Monitor.Exit(this); }}编译,运行,单击按钮,标签控件应显示 10 。