《《多线程技术》PPT课件.ppt》由会员分享,可在线阅读,更多相关《《多线程技术》PPT课件.ppt(48页珍藏版)》请在三一办公上搜索。
1、第六讲 C#多线程技术,主要内容,6.1 线程概述6.2.NET对多线程的支持6.3 一个多线程程序6.4 线程的优先级 6.5 线程的同步 6.6 多线程的自动管理 6.7 应用实例,6.1 线程概述,进程:是应用程序的一个运行例程,是应用程序的一次动态执行过程。线程:是进程中的一个执行单元;是操作系统分配CPU时间的基本单元。Windows是一个支持多线程的系统。一个进程可以包含若干个线程。,多线程的概念,多线程:在同一时间执行多个任务的功能,称为多线程或自由线程。多线程的优点:可以同时完成多个任务;可以使程序的响应速度更快;可以让占用大量处理时间的任务或当前没有进行处理的任务定期将处理时
2、间让给别的任务;可以随时停止任务;可以设置每个任务的优先级以优化程序性能。主要缺点:对资源的共享访问可能造成冲突(对共享资源的访问进行同步或控制);程序的整体运行速度减慢等等。,在C#应用程序中,第一个线程总是Main()方法,因为第一个线程是由.NET运行库开始执行的,Main()方法是.NET运行库选择的第一个方法。后续的线程由应用程序在内部启动,即应用程序可以创建和启动新的线程。,6.2.NET对多线程的支持,在.NET程序设计中,线程是使用Thread类(或Timer类(线程计数器)、ThreadPool类(线程池)来处理的,这些类在System.Threading命名空间中:usin
3、g System.Threading;Thread类:(实现线程的主要方法)一个Thread实例管理一个线程,即执行序列。通过简单实例化一个Thread对象,就可以创建一个线程,然后通过Thread对象提供的方法对线程进行管理。Timer类:适用于间隔性的完成任务。ThreadPool:适用于多个小的线程。,Thread类的主要属性,1、CurrentThread:获取当前正在运行的线程。2、Name:获取或设置线程的名称。3、Priority:获取或设置线程的优先级。4、TreadState:获取或设置线程的当前状态。5、IsBackground:指示线程是否为后台线程。6、IsAlive:
4、指示当前线程的执行状态。7、CurrentContext:获取线程其中执行的当前上下文。,Thread类的主要方法,1、Abort:终止线程。2、GetDomain:返回当前线程正在其中运行的当前域。3、Interrupt:中断处于WaitSleepJoin线程状态的线程。4、Join:阻塞调用线程,直到某个线程终止时为止。5、ResetAbort:取消为当前线程请求的Abort6、Resume:继续已挂起的线程。7、Sleep:将当前线程阻塞指定的毫秒数。8、Start:启动线程。9、Suspend;挂起线程。,Thread类中线程的状态,由Thread类的TreadState属性来确定:A
5、bort、AbortRequested、BackGround、Running、Stopped、StopRequested、Suspended、SuspendRequsted、Unstarted、WaitSleepJoin,线程的建立与启动,新建一个线程的过程:只需将其声明并为其提供线程起始点处的方法委托,再用Thread.Start()方法启动该线程(1)声明:Thread a;(2)实例化a=new Thread(new ThreadStart(b);其中,b为新建过程中执行的过程名。(3)调用Thread.Start()方法启动该线程a.Start();,例题1:线程的建立和启动,usin
6、g System;using System.Threading;public class Apublic void ff()/线程启动时调用此方法Console.WriteLine(A.ff()方法在另一个线程上运行!);Thread.Sleep(3000);/将线程阻塞一定时间Console.WriteLine(终止工作线程调用此实例方法!);public static void gg()Console.WriteLine(A.gg()方法在另一个线程上运行!);Thread.Sleep(5000);/将线程阻塞一定时间Console.WriteLine(终止工作线程调用此静态方法!);,p
7、ublic class Bpublic static void Main()Console.WriteLine(*线程简单示例!*);A a=new A();Thread s1=new Thread(new ThreadStart(a.ff);s1.Start();Console.WriteLine(启动新线程ff()方法后,被Main()线程调用!);Thread s2=new Thread(new ThreadStart(A.gg);s2.Start();Console.WriteLine(启动新线程gg()方法后,被Main()线程调用!);Console.ReadLine();,线程的
8、挂起、恢复与终止,线程的挂起(或暂停)(1)调用Thread.Sleep()方法将线程挂起。注:Sleep()方法指定的时间以毫秒为单位。(2)调用s1.Suspend()方法将线程挂起区别:前者为静态方法,并且使线程立即暂停一定时间;后者为实例方法,不会使线程立即停止执行,直到线程到达安全点之后,它才将该线程暂停。线程的恢复与终止调用Resume()方法将线程恢复;调用Abort()方法将线程终止;,其他与操作线程相关的方法,Join():使一个线程等待另一个线程停止Interrupt():中断处于JoinWaitSleep线程状态的线程。,6.3 一个多线程程序,例题2 使用两个线程显示计
9、数。该示例的核心是方法DisplayNumbers(),它累加一个数字,并定期显示每次累加的结果:using System;using System.Threading;namespace ConsoleThread class ThreadApp static int interval;,static void DisplayNumbers()/获取当前运行线程的Thread对象实例 Thread thisThread=Thread.CurrentThread;Console.WriteLine(线程:+thisThread.Name+已开始运行.);/循环计数直到结束,在指定的间隔输出当前
10、计数值 for(int i=1;i=8*interval;i+)if(i%interval=0)Console.WriteLine(thisThread.Name+:当前计数为+i);Console.WriteLine(线程+thisThread.Name+完成.);,static void Main(string args)/获取用户输入的数字 Console.Write(请输入一个数字:);interval=int.Parse(Console.ReadLine();/定义当前主线程线程对象的名字 Thread thisThread=Thread.CurrentThread;thisThre
11、ad.Name=Main Thread;/建立新线程对象 ThreadStart workerStart=new ThreadStart(DisplayNumbers);Thread workerThread=new Thread(workerStart);workerThread.Name=Worker Thread;workerThread.IsBackground=true;workerThread.Start();/启动新线程DisplayNumbers();/主线程同步进行计数,6.4 线程的优先级,线程是根据其优先级来调度的,每个线程都有特定的优先级。每个线程在创建时其优先级为:T
12、hreadPriority.Normal线程的优先级定义为ThreadPriority枚举类型,如下表:,例题3:修改上述例题,对Main()方法做如下修改,观察修改线程的优先级的效果:ThreadStart workerStart=new ThreadStart(DisplayNumbers);Thread workerThread=new Thread(workerStart);workerThread.Name=Worker Thread;workerThread.Priority=ThreadPriority.AboveNormal;结果:一旦工作线程被启动,主线程不再运行,直到工作线
13、程结束后主线程才重新计算。,6.5 线程的同步,使用线程的一个重要方面是同步访问多个线程访问的任何变量。背景:当多个线程共享数据,其中一个或多个线程要修改数据时,有可能引起数据不统一等问题。同步:是指在某一时刻只有一个线程可以访问某共享数据1、同步的含义同步问题的产生,主要是由于在高级语言的源代码中,大多数情况下看起来是一条语句,但在最后编译好的汇编语言机器码中则会被翻译为许多条语句,从而在操作系统调度时被划分到不同的时间片中。例如:,message+=Hello world!;这条语句在C#语法上是一条语句,但在执行代码时,实际上它涉及到许多操作。需要重新分配内存以存储更长的新字符串,需要设
14、置变量message使之指向新的内存,需要复制实际文本等。,2、在C#中处理同步,通过对指定对象的加锁和解锁可以实现同步代码段的访问。在.NET的System.Threading命名空间中提供了Monitor类来实现加锁与解锁。该类中的方法都是静态的。如下表:,C#中 lock关键字提供了与Monitoy.Enter和Monitoy.Exit同样的功能,这种方法用在你的代码段不能被其他独立的线程中断的情况。通过对Monitor类的简易封装,lock为同步访问变量提供了一个非常简单的方式,其用法如下:lock(x)/使用x的语句lock语句把变量放在圆括号中,以包装对象,称为独占锁或排它锁。当执
15、行带有lock关键字的复合语句时,独占锁会保留下来。当变量被包装在独占锁中时,其他线程就不能访问该变量。如果在上面的代码中使用独占锁,在执行复合语句时,这个线程就会失去其时间片。如果下一个获得时间片的线程试图访问变量,就会被拒绝。Windows会让其他线程处于睡眠状态,直到解除了独占锁为止。,例题4:使用lock同步线程。本示例建立了10个线程,using System;using System.Threading;/银行帐户类class Account int balance;/余额 Random r=new Random();public Account(int initial)balan
16、ce=initial;,/取钱 int Withdraw(int amount)if(balance=amount)Console.WriteLine(原有余额:+balance);Console.WriteLine(支取金额:-+amount);balance=balance-amount;Console.WriteLine(现有余额:+balance);return amount;else return 0;/拒绝交易,/测试交易 public void DoTransactions()/支取随机的金额100次 for(int i=0;i 100;i+)Withdraw(r.Next(1,
17、100);class TestApp public static void Main()/建立10个线程同时进行交易 Thread threads=new Thread10;Account acc=new Account(1000);,for(int i=0;i 10;i+)Thread t=new Thread(new ThreadStart(acc.DoTransactions);threadsi=t;for(int i=0;i 10;i+)threadsi.Start();,例题5:使用Thread类的方法同步进程,using System;using System.Threading;
18、class Astatic void Main()A obj1=new A();Thread thread1=new Thread(new ThreadStart(obj1.ff);thread1.Name=第一个间隔40ms循环打印线程;B obj2=new B();,Thread thread2=new Thread(new ThreadStart(obj2.gg);thread2.Name=第一个间隔100ms循环打印线程;thread2.Start();/thread2.Join();thread1.Start();Console.ReadLine();private void ff(
19、)for(int i=0;i10;i+)Console.WriteLine(Thread.CurrentThread.Name+:Current count is+i);Thread.Sleep(40);,class Bpublic void gg()for(int i=0;i5;i+)Console.WriteLine(Thread.CurrentThread.Name+:add+i);Thread.Sleep(100);,lock 与Monitor 区别lock 锁定一段代码,Monitor 锁定一个对象。当多线程公用一个对象时,也会出现和公用代码类似的问题,这种问题就不应该使用lock
20、关键字了,这里需要用到System.Threading 中的一个类Monitor,我们可以称之为监视器,Monitor 提供了使线程共享资源的方案。Monitor 类可以锁定一个对象,一个线程只有得到这把锁才可以对该对象进行操作。对象锁机制保证了在可能引起混乱的情况下一个时刻只有一个线程可以访问这个对象。,Monitor 必须和一个具体的对象相关联,但是由于它是一个静态的类,所以不能使用它来定义对象,而且它的所有方法都是静态的,不能使用对象来引用。下面代码说明了使用Monitor 锁定一个对象的情形:.Queue oQueue=new Queue();.Monitor.Enter(oQueue
21、);./现在oQueue 对象只能被当前线程操纵了Monitor.Exit(oQueue);/释放锁如上所示,当一个线程调用 Monitor.Enter()方法锁定一个对象时,这个对象就归它所有了,其它线程想要访问这个对象,只有等待它使用Monitor.Exit()方法释放,锁。(在一个线程运行的代码里面调用Monitor.Enter(对象),那么其它线程就不能使用该对象了)为了保证线程最终都能释放锁,你可以Monitor.Exit()方法写在try-catch-finally 结构中的finally代码块里。对于任何一个被 Monitor 锁定的对象,内存中都保存着与它相关的一些信息:其一是
22、现在持有锁的线程的引用;其二是一个预备队列,队列中保存了已经准备好获取锁的线程;其三是一个等待队列,队列中保存着当前正在等待这个对象状态改变的队列的引用。获取的过程:当拥有对象锁的线程准备释放锁时,它使用 Monitor.Pulse()方法通知等待队列中的第一个线程,于是该线程被转移到预备队列中,当对象锁被释放时,在预备队列中的线程可以立即获得对象锁。,3、同步时要注意的问题,线程同步非常重要,但只在需要时使用也是非常重要的。因为这会降低性能。原因有两个:首先,在对象上放置和解开锁会带来某些系统开销,但这些系统开销都非常小。第二个原因更为重要,线程同步使用得越多,等待释放对象的线程就越多。如果
23、一个线程在对象上放置了一个锁,需要访问该对象的其他线程就只能暂停执行,直到该锁被解开,才能继续执行。因此,在lock块内部编写的代码越少越好,以免出现线程同步错误。lock语句在某种意义上就是临时禁用应用程序的多线程功能,也就临时删除了多线程的各种优势。,6.6多线程的自动管理,在多线程的程序中,经常会出现两种情况:一种情况:应用程序中,线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应这一般使用 ThreadPool(线程池)来解决;另一种情况:线程平时都处于休眠状态,只是周期性地被唤醒这一般使用 Timer(定时器)来解决;,1、多线程的自动管理(定时器Timer)Tim
24、er 类:设置一个定时器,定时执行用户指定的函数。定时器启动后,系统将自动建立一个新的线程,执行用户指定的函数。初始化一个 Timer 对象:Timer timer=new Timer(timerDelegate,s,1000,1000);/第一个参数:指定了TimerCallback 委托,表示要执行的方法;/第二个参数:一个包含回调方法要使用的信息的对象,或者为空引用;,/第三个参数:延迟时间计时开始的时刻距现在的时间,单位是毫秒,指定为“0”表示立即启动计时器;/第四个参数:定时器的时间间隔计时开始以后,每隔这么长的一段时间,TimerCallback 所代表的方法将被调用一次,单位也是
25、毫秒。指定 Timeout.Infinite 可以禁用定期终止。Timer.Change()方法:修改定时器的设置。(这是一个参数类型重载的方法).timer.Change(1000,2000);,2、多线程的自动管理(线程池)ThreadPool类提供一个由系统维护的线程池(可以看作一个线程的容器),该容器需要 Windows 2000以上系统支持,因为其中某些方法调用了只有高版本的Windows 才有的API 函数。将线程安放在线程池里,需使用 ThreadPool.QueueUserWorkItem()方法,该方法的原型如下:/将一个线程放进线程池,该线程的Start()方法将调用Wai
26、tCallback 代理对象代表的函数,public static bool QueueUserWorkItem(WaitCallback);/重载的方法如下,参数object 将传递给WaitCallback 所代表的方法public static bool QueueUserWorkItem(WaitCallback,object);ThreadPool 类是一个静态类,你不能也不必要生成它的对象。而且一旦使用该方法在线程池中添加了一个项目,那么该项目将是无法取消的。在这里你无需自己建立线程,只需把你要做的工作写成函数,然后作为参数传递给ThreadPool.QueueUserWorkIt
27、em()方法就行了,传递的方法就是依靠WaitCallback 代理对象,而线程的建立、管理、运行等工作都是由系统自动完成的.,ManualResetEvent 对象ManualResetEvent 对象,该对象就像一个信号灯,可以利用它的信号来通知其它线程。初始化该对象时,用户可以指定其默认的状态(有信号/无信号);在初始化以后,该对象将保持原来的状态不变,直到它的 Reset()或者Set()方法被调用:Reset()方法:将其设置为无信号状态;Set()方法:将其设置为有信号状态。,WaitOne()方法:使当前线程挂起,直到ManualResetEvent 对象处于有信号状态,此时该线
28、程将被激活。然后,程序将向线程池中添加工作项,这些以函数形式提供的工作项被系统用来初始化自动建立的线程。当所有的线程都运行完了以后,ManualResetEvent.Set()方法被调用,因为调用了ManualResetEvent.WaitOne()方法而处在等待状态的主线程将接收到这个信号,于是它接着往下执行,完成后边的工作。,6.7 应用实例,(两个关于线程或进程的例子)综合例题1:通过Process类获取系统进程列表。运行界面如下图所示:,using System.Diagnostics;private void button1_Click(object sender,System.Ev
29、entArgs e)listBox1.Items.Clear();listBox1.Items.Add(编号+名称);/将所有的系统进程显示在列表框中foreach(Process a in Process.GetProcesses()string name=a.ProcessName;int id=a.Id;listBox1.Items.Add(id.ToString()+name);,综合例题2在本例中,介绍怎样获取当前所有的线程,并显示它们的信息。在本例中,应掌握如何获取当前线程的方法,同时获取线程的进一步信息,了解线程的一些基本知识和基本概念。运行界面如下图所示:,综合例题3 跨线程操
30、作在C#中,经常用到这样一个场景,Windows Form程序启动一个工作者线程执行一部分工作,这样做是为了避免速度慢的工作如果直接调用会使得主Form停止响应一段时间。工作线程处理中可能想操作某个主线程的Windows Form的Control,比如按钮,ListView等等更新工作状态之类,直接控制是不行的,不能够跨线程操作另一个线程创建的Windows Form控件。要使用委托去调用。,private delegate void SetTextCallback(System.Windows.Forms.Control control,string text);public void AppendData(System.Windows.Forms.Control control,string text)if(control.InvokeRequired)SetTextCallback d=new SetTextCallback(AppendData);control.Invoke(d,new object control,text);else/control.Text+=text+rn;(System.Windows.Forms.TextBox)control).AppendText(text);,