收藏 分享(赏)

C#与.Net程序设计课件第9章.ppt

上传人:11xg27ws 文档编号:8652175 上传时间:2019-07-07 格式:PPT 页数:49 大小:928KB
下载 相关 举报
C#与.Net程序设计课件第9章.ppt_第1页
第1页 / 共49页
C#与.Net程序设计课件第9章.ppt_第2页
第2页 / 共49页
C#与.Net程序设计课件第9章.ppt_第3页
第3页 / 共49页
C#与.Net程序设计课件第9章.ppt_第4页
第4页 / 共49页
C#与.Net程序设计课件第9章.ppt_第5页
第5页 / 共49页
点击查看更多>>
资源描述

1、,第 9 章多线程,9.1 一个简单的多线程应用程序 9.2 线程及其实现方法 9.3 线程的同步控制 9.4 线程池 9.5 线程对控件的访问,本 章 内 容,9.1 一个简单的多线程应用程序,本小节创建的多线程应用程序一共包含两个线程,这两个线程并发地在屏幕上输出相关的字符串。 程序的关键代码如下:class Apublic static int n = 0;public void f()for (int i = 0; i 10; i+)Console.WriteLine(“f()在输出:0“, A.n);A.n+;Thread.Sleep(100);,让当前线程睡眠100毫秒,9.1 一

2、个简单的多线程应用程序,class B public static void g() for (int i = 0; i 10; i+) Console.WriteLine(“g()在输出:0“, A.n);A.n+;Thread.Sleep(100);static void Main(string args)A a = new A(); ThreadStart thst1 = new ThreadStart(a.f);ThreadStart thst2 = new ThreadStart(B.g);Thread th1 = new Thread(thst1);Thread th2 = new

3、 Thread(thst2);th1.Start();th2.Start();Console.ReadKey();,可见,每个线程实际上是Thread类的对象,它是通过Thread类的构造函数来创建;并且每个线程都与既定的方法相关联,让当前线程睡眠100毫秒,执行线程th1和th2实际上是执行方法a.f()和方法B.g()。,由这个例子可以看到,Thread类、委托类型ThreadStart等是多线程程序设计中的核心内容。,9.2 线程及其实现方法,9.2.1 线程的概念,线程的概念与程序、进程的概念密切相关。程序是程序员编写的静态代码文本。进程则是程序的一次动态执行过程,进程运行时需要占用装

4、载程序代码(编译后的可执行代码)以及存放其所需数据的内存空间和其他的机器资源(如文件等),当进程终止时这些内存空间和资源也随之释放。显然,同一个程序,它可以被多次加载到不同的内存区域中、使用不同的机器资源,从而形成多个不同的进程,即一个程序可以形成多个进程。一个进程是由多个执行单元组成,每个执行单元就是一个线程,即进程是由多个线程组成。每个线程都共享着其进程所占用的内存空间和机器资源(如堆栈、CPU、寄存器等),实际上,一个线程是一组机器指令以及它共享的内存和资源。,9.2 线程及其实现方法,9.2.1 线程的概念,线程和进程的主要区别在于:进程是由多个线程组成,即线程是进程的一个组成部分。线

5、程的划分尺度小,具有较高的并发效率。进程独占相应的内存和资源(其他进程不能使用),线程则是共享进程所拥有的内存和资源(其他线程也可以使用),从而极大地提高运行效率。进程提供多个线程执行控制,而每个线程只能有一个运行入口、顺序执行序列和出口(“线序”执行)。进程可以独立执行,但线程不能独立执行,而必须依赖于进程所提供的环境。,9.2 线程及其实现方法,9.2.2 线程的实现方法,线程的创建和应用主要是由Thread类和ThreadStart委托来实现。Thread类的构造函数和主要方法说明如下: 构造函数 Thread类构造函数的作用是用于创建线程,它主要有两个重载版本:public Threa

6、d(ThreadStart start) public Thread(ParameterizedThreadStart start)其中,参数start是ThreadStart类型或ParameterizedThreadStart类型的变量。这两种委托类型的声明如下:public delegate void ThreadStart() public delegate void ParameterizedThreadStart(Object obj),构造函数创建的线程关联没有参数的方法,构造函数创建的线程关联带一个object类型参数的方法,每个线程都必须关联一个无返回类型的方法(称为线程方法

7、),如果关联的方法无参数,则用第一个构造函数创建线程;如果关联的方法带一个参数,则用第二个构造函数创建线程。 当然,线程间数据的传递也可以使用对象的成员变量或方法来实现,这也是常用的方法,但要在线程的同步控制下进行。,9.2 线程及其实现方法,9.2.2 线程的实现方法,例如,下面代码先定义类A,它有两个静态方法f()和g(),其中后者带有object类型的参数obj: class A public static void f() Console.WriteLine(“这是关联方法f()的线程“); public static void g(object obj) Console.WriteL

8、ine(“这是关联方法g()的线程:“+obj.ToString(); ,9.2 线程及其实现方法,9.2.2 线程的实现方法,然后用上述两种构造函数分别通过委托类型ThreadStart和ParameterizedThreadStart创建线程th1和th2,它们分别关联方法f()和g(),并(在Main()方法中)执行它们:ThreadStart thst = new ThreadStart(A.f); ParameterizedThreadStart pthst = new ParameterizedThreadStart(A.g); Thread th1 = new Thread(th

9、st); /关联方法f() Thread th2 = new Thread(pthst); /关联方法g(),带一个参数 th1.Start(); /启动线程th1(执行方法f()) th2.Start(200); /启动线程th2(执行方法g(),并将200作为参数值传给该方法),执行后,将输出如下结果:这是关联方法f()的线程 这是关联方法g()的线程:200,需要注意的是,线程关联的方法必须与所使用的委托类型相一致,返回类型必须为void,且在创建委托对象时关联的方法必须是已经确定了的。这些方法通常是类的静态方法和对象的方法。,9.2 线程及其实现方法,9.2.2 线程的实现方法,Sta

10、rt()方法 该方法的作用是用于启动已经创建的线程,线程将进入Running状态(线程刚创建完时是处于Unstarted状态)。例如,以下代码是在前面代码中已经出现过的调用语句:th1.Start(); th2.Start(200);Abort()方法 该方法用于终止线程,使线程进入AbortRequested状态。例如,终止线程th的语句是:th.Abort();,9.2 线程及其实现方法,9.2.2 线程的实现方法,Suspend()方法和Resume()方法 Suspend()方法用于挂起线程,使线程进入SuspendRequested状态;Resume()方法则用于将被挂起的线程重新工

11、作,使得它进入Running状态。Join()方法 假设在线程th1中对线程th2执行下列语句:th2.Join();这表示,将阻止线程th1的执行,直到th2执行完为止(才继续执行th1)。如果写成下列的形式,则表示阻止线程th1,直到500毫秒以后th1才运行:th2.Join(500);,9.2 线程及其实现方法,9.2.2 线程的实现方法,以下是Thread类的主要属性: CurrentCulture属性 该属性用于获取或设置当前线程的区域性。CurrentThread属性 获取当前正在运行的线程。CurrentUICulture属性 获取或设置资源管理器使用的当前区域性,以便在运行时

12、查找区域性特定的资源。IsAlive属性 该属性返回指示当前线程执行状态的值。IsBackground属性 该属性用于获取或设置指示当前线程是否为后台线程。值为true时表示为后台线程,这时该线程随着主进程的结束而结束,而不管该线程是否已经运行结束;值为false(默认值)时表示为前台线程,只有所有的前台线程运行结束后,主线程才能终止。,9.2 线程及其实现方法,9.2.2 线程的实现方法,ManagedThreadId属性 获取当前托管线程的唯一标识符。Name属性 获取或设置线程的名称。ThreadState属性 返回当前线程的状态。线程的状态包括Running、StopRequested

13、、SuspendRequested、Background、Unstarted、Stopped、WaitSleepJoin、Suspended、AbortRequested和Aborted。,9.2 线程及其实现方法,9.2.3 线程的优先级,线程的优先级是用Thread类的Priority属性来设置,其值集是一个枚举,即Lowest, BelowNormal, Normal, AboveNormal, Highest,它们的优先级别依次从低到高,Priority属性的默认设置是ThreadPriority.Normal。需要注意的是,操作系统并不能够保证拥有高优先级的线程每次都能够获得比低优先

14、级线程更高执行权限,这跟操作系统的调度算法有关。,9.3 线程的同步控制,9.3.1 为什么要同步控制,【例9.1】 存在同步访问问题的多线程程序。(为什么要同步控制)创建控制台应用程序BankTransfering,它只是简单地模拟银行用户进行转帐和取款的程序: class Bankprivate double account1 = 2500;private double account2 = 1000;public void transfering() /转帐Console.WriteLine(“转帐 前 帐户account1还剩余的金额:“ + account1.ToString();C

15、onsole.Write(“转帐金额(元):“);double sum = double.Parse(Console.ReadLine(); /输入转帐金额if (sum account1)Console.WriteLine(“转帐金额超出了帐户account1所剩的金额,“+“转帐失败!“);return;,9.3 线程的同步控制,9.3.1 为什么要同步控制,account1 = account1 - sum; account2 = account2 + sum;Console.WriteLine(“转帐 后 帐户account1还剩余的金额:“ + account1.ToString()

16、; public void fetching() /取款Thread.Sleep(100);account1 = account1 - 2000; /取款2000元static void Main(string args)Bank a = new Bank();Thread user1 = new Thread(new ThreadStart(a.transfering);Thread user2 = new Thread(new ThreadStart(a.fetching);user1.Start();user2.Start(); Console.ReadKey(); ,9.3 线程的同步

17、控制,9.3.1 为什么要同步控制,程序中的类Bank定义了两个方法:fetching()和transfering(),它们分别用于实现取款和转帐操作,并基于这两个方法分别创建了线程user1和user2。程序运行时,user1和user2几乎是同时开始工作,随后user1从键盘接收转帐金额,然后完成转帐操作;但user2的“动作”比较快,立刻就取出2000元。程序运行结果如下图所示。,可以看到,user1查询帐户account1时,明明显示了还剩2500元的信息,但在执行从account1向account2转2000元时,却出现了操作失败的提示(即使转帐操作成功了,结果显示的剩余金额也不对)

18、。其原因在于,恰好在user1等待接收从键盘输入的转帐金额时,user2从帐户account1上提走了2000元。显然,我们不希望发生这种情况,这就需要线程的同步控制来解决。,9.3 线程的同步控制,9.3.2 使用ManualResetEvent类,ManualResetEvent类的作用是:通知一个或多个正在等待的线程已发生事件。ManualResetEvent类对象有两种状态:有信号状态和无信号状态。其状态常通过两种方法设置:一种是使用构造函数,另一种是对象方法。例如:ManualResetEvent mre = new ManualResetEvent(false); /初始化mre为

19、无信号状态 ManualResetEvent mre = new ManualResetEvent(true); /初始化mre为有信号状态mre.Reset(); /使mre处于无信号状态 mre.Set(); /使mre处于有信号状态,使用构造函数,使用对象方法,9.3 线程的同步控制,9.3.2 使用ManualResetEvent类,当ManualResetEvent类对象处于无信号状态时,调用该对象WaitOne()方法的线程将被阻止运行(暂停);当该对象变为处于有信号状态(WaitOne()方法收到信号)时,WaitOne()方法将解除该线程的暂停状态,使它继续运行。,据此,我们就

20、可以实现多线程的同步控制。方法是:将被视为一体的语句序列置于Reset()和Set()方法之间(称为“加锁”),与它们并发的线程,在读取共享变量前先调用WaitOne()方法;这样在执行这些语句序列时由于ManualResetEvent类对象无信号,因此该线程被暂停,直到它们执行完了以后才有信号,该线程才能继续执行,因而避免读取不正确的数据,从而实现线程的同步控制。,9.3 线程的同步控制,9.3.2 使用ManualResetEvent类,下面分两种情况来介绍如何对多线程进行同步控制。 1. 单线程的加锁 对于上节介绍的程序BankTransfering,为解决其同步问题,可将代码修改如下(

21、红色部分):class Bankprivate double account1 = 2500;private double account2 = 1000;/创建ManualResetEvent类的对象mrepublic ManualResetEvent mre = new ManualResetEvent(false); public void transfering() /转帐mre.Reset(); /设置对象mre处于无信号状态Console.WriteLine(“转帐 前 帐户account1还剩余的金额:“ +account1.ToString();Console.Write(“转

22、帐金额(元):“);double sum = double.Parse(Console.ReadLine();,9.3 线程的同步控制,9.3.2 使用ManualResetEvent类,double sum = double.Parse(Console.ReadLine(); if (sum account1)Console.WriteLine(“转帐金额超出了帐户account1所剩的金额,“ +“转帐失败!“);return;account1 = account1 - sum;account2 = account2 + sum;Console.WriteLine(“转帐 后 帐户acco

23、unt1还剩余的金额:“ +account1.ToString();mre.Set(); /设置对象mre处于有信号状态public void fetching() /取款/阻止当前线程(线程user2)的运行,直到收到对象mre发的信息mre.WaitOne(); Thread.Sleep(100);account1 = account1 - 2000; ,9.3 线程的同步控制,9.3.2 使用ManualResetEvent类,double sum = double.Parse(Console.ReadLine(); if (sum account1)Console.WriteLine(

24、“转帐金额超出了帐户account1所剩的金额,“ +“转帐失败!“);return;account1 = account1 - sum;account2 = account2 + sum;Console.WriteLine(“转帐 后 帐户account1还剩余的金额:“ +account1.ToString();mre.Set(); /设置对象mre处于有信号状态public void fetching() /取款/阻止当前线程(线程user2)的运行,直到收到对象mre发的信息mre.WaitOne(); Thread.Sleep(100);account1 = account1 - 2

25、000; ,上述代码只使用了一个ManualResetEvent类对象,仅对一个线程中的语句序列进行加锁。如果有多个线程中的语句序列需要加锁,那应该怎么办呢?这就涉及到多线程的加锁问题。,9.3 线程的同步控制,9.3.2 使用ManualResetEvent类,2. 多线程的加锁 一个ManualResetEvent类对象只能对一个线程中的语句序列进行加锁;如果需要对多个线程中的语句序列进行加锁,就需要创建与线程数量一样多的ManualResetEvent类对象。【例9.2】 下列是控制台应用程序BankTransfering2中文件Program.cs的代码,它仍然模拟银行转帐、查账的功能

26、,但对代码进行了简化(该程序需要解决多线程的同步控制问题):,9.3 线程的同步控制,9.3.2 使用ManualResetEvent类,class Bankprivate double account1 = 2500;private double account2 = 1000;public void transfering() /将100元从帐户account1转到帐户account2account1 = account1 - 100;Thread.Sleep(100);account2 = account2 + 100;public void transfering2() /将300元从

27、帐户account2转到帐户account1account1 = account1 + 300;Thread.Sleep(200);account2 = account2 - 300;public void querying() /查询帐户account1和account2上的余额Console.WriteLine(“帐户account1上的余额为:0 元“, account1);Console.WriteLine(“帐户account2上的余额为:0 元“, account2); ,9.3 线程的同步控制,9.3.2 使用ManualResetEvent类,static void Main(

28、string args)Bank a = new Bank();Thread user1 = new Thread(new ThreadStart(a.transfering); /转帐用户1Thread user2 = new Thread(new ThreadStart(a.transfering2); /转帐用户2Thread user3 = new Thread(new ThreadStart(a.querying); /查账用户user1.Start(); /执行转帐(account1到account2)user2.Start(); /执行转帐(account2到account1)u

29、ser3.Start(); /查账用户Console.ReadKey();,显然,该结果并不是我们预期的结果。为了查账用户(线程user3)能看到一致的数据,需要对方法transfering()和方法transfering2()中的代码都应该加锁,这就是涉及到对两个线程中的语句进行加锁的问题。,9.3 线程的同步控制,9.3.2 使用ManualResetEvent类,实现对两个线程中的语句进行加锁: (1)先创建一个包含两个ManualResetEvent类对象的ManualResetEvent数组mres: ManualResetEvent mres = new ManualResetEv

30、ent(false), new ManualResetEvent(false) ; (2)用数组mres中的两个对象分别对方法transfering()和方法transfering2()中的代码进行加锁;(3)在方法querying()中查询语句之前调用WaitHandle.WaitAll()方法,该方法的参数类型是ManualResetEvent数组,其作用是:当数组中所有的对象都接收到信号后才允许方法querying()继续执行。,9.3 线程的同步控制,9.3.2 使用ManualResetEvent类,修改后的代码:class Bankprivate double account1 =

31、 2500;private double account2 = 1000;ManualResetEvent mres = new ManualResetEvent(false), new ManualResetEvent(false) ; /创建包含两个ManualResetEvent类对象的数组public void transfering() /将100元从帐户account1转到帐户account2mres0.Reset();account1 = account1 - 100;Thread.Sleep(100);account2 = account2 + 100;mres0.Set();

32、,9.3 线程的同步控制,9.3.2 使用ManualResetEvent类,public void transfering2() /将300元从帐户account2转到帐户account1mres1.Reset();account1 = account1 + 300;Thread.Sleep(200);account2 = account2 - 300;mres1.Set();public void querying() /查询帐户account1和account2上的余额WaitHandle.WaitAll(mres);Console.WriteLine(“帐户account1上的余额为:

33、0 元“, account1);Console.WriteLine(“帐户account2上的余额为:0 元“, account2);,该结果与预想的完全一致,这说明已经正确实现线程的同步控制。,9.3 线程的同步控制,9.3.3 使用AutoResetEvent类,ManualResetEvent的缺点:当使用ManualResetEvent类来进行多线程的同步控制时,创建的ManualResetEvent类对象的数量要与线程的个数相同,这使程序代码显得比较累赘。AutoResetEvent的特点:与ManualResetEvent类不同的是,在执行AutoResetEvent的Set()方

34、法时,AutoResetEvent对象仅发出“一条”信号,这样也就仅仅“消掉”一个WaitOne()方法;如果还有其他WaitOne()方法在等待信号,那么AutoResetEvent对象会自动变为无信号状态(如果没有就不改变其状态),直到再次执行一个Set()方法才能“消掉”下一个WaitOne()方法。,因此,使用AutoResetEvent类来实现线程的同步控制,程序代码会显得更为简洁、编写效率也会更高。 根据这一点,我们只需要创建一个AutoResetEvent对象,就可以完成对多个线程的同步控制。,9.3 线程的同步控制,9.3.3 使用AutoResetEvent类,【例9.3】

35、修改程序BankTransfering2(见例9.2),使用AutoResetEvent类实现对其所涉及线程的同步控制。class Bankprivate double account1 = 2500;private double account2 = 1000;AutoResetEvent are = new AutoResetEvent(false);public void transfering() /将100元从帐户account1转到帐户account2are.Reset();account1 = account1 - 100;Thread.Sleep(100);account2 =

36、 account2 + 100;are.Set();,9.3 线程的同步控制,9.3.3 使用AutoResetEvent类,public void transfering2() /将300元从帐户account2转到帐户account1are.Reset();account1 = account1 + 300;Thread.Sleep(200);account2 = account2 - 300;are.Set();public void querying() /查询帐户account1和account2上的余额are.WaitOne(); are.WaitOne();Console.Wri

37、teLine(“帐户account1上的余额为:0 元“, account1);Console.WriteLine(“帐户account2上的余额为:0 元“, account2);,可以看到,在该程序中只创建了一个AutoResetEvent类对象,就可以实现对多个线程的同步控制,这就是AutoResetEvent类的优势。但要注意,有多少个Set()方法就应该多少个WaitOne()方法与之对应,否则会出现无限等待或其他问题。,9.4 线程池,为什么要用线程池? 如果频繁地对大量的线程执行创建、销毁等操作,其代价是昂贵的,可能导致系统性能严重下降。为避免创建、销毁线程所付出的大量额外时间,

38、一种解决方法是使用线程池。什么是线程池? 线程池(ThreadPool)可以简单地理解为存放线程的容器。线程池中存放若干线程,当有任务要执行的时候,从线程池中唤醒一个线程,令它执行该任务;任务执行完毕后,重新将线程放回线程池(而不是销毁),并令其处于休眠状态。这样,就不需要对线程进行创建和销毁操作,从而节省时间并使系统更加稳定。,9.4 线程池,实际上,线程池是一种线程管理器,由ThreadPool类提供的方法来维护线程。其中,ThreadPool.QueueUserWorkItem()方法用于将线程存放到线程池中,该方法原型如下:public static bool QueueUserWor

39、kItem(WaitCallback);被放到线程池中的线程的Start()方法将调用WaitCallback代理对象代表的函数。该方法的重载定义如下:public static bool QueueUserWorkItem(WaitCallback, object);参数object将被传递给WaitCallback所代表的方法,由此可以实现参数传递。,9.4 线程池,实际上,线程池是一种线程管理器,由ThreadPool类提供的方法来维护线程。其中,ThreadPool.QueueUserWorkItem()方法用于将线程存放到线程池中,该方法原型如下:public static bool

40、 QueueUserWorkItem(WaitCallback);被放到线程池中的线程的Start()方法将调用WaitCallback代理对象代表的函数。该方法的重载定义如下:public static bool QueueUserWorkItem(WaitCallback, object);参数object将被传递给WaitCallback所代表的方法,由此可以实现参数传递。,利用线程池,我们无需显式创建线程,而只需将要完成的任务写成函数,然后将之作为参数通过WaitCallback代理对象传递给QueueUserWorkItem()方法即可,而后由线程池自动建立、管理、运行相应的线程。,

41、9.4 线程池,【例9.4】 使用线程池的简单例子。public class MyThreadClasspublic void MyMethod(object parameter)string str = (string)parameter;for (int i = 1; i 10; i+)Console.Write(str + “(任务“ + i + “)n“);Thread.Sleep(100);,9.4 线程池,static void Main(string args)MyThreadClass instance = new MyThreadClass();string myParame

42、ter = “线程1.“;ThreadPool.QueueUserWorkItem(new WaitCallback(instance.MyMethod), myParameter);myParameter = “线程2.“;ThreadPool.QueueUserWorkItem(new WaitCallback(instance.MyMethod), myParameter);myParameter = “线程3.“;ThreadPool.QueueUserWorkItem(new WaitCallback(instance.MyMethod), myParameter);Console.

43、ReadLine();,9.4 线程池,线程1.(任务1) 线程2.(任务1) 线程1.(任务2) 线程2.(任务2) 线程1.(任务3) 线程2.(任务3) 线程1.(任务4) 线程2.(任务4) 线程1.(任务5) 线程2.(任务5) 线程1.(任务6) 线程2.(任务6) 线程1.(任务7) 线程2.(任务7) 线程1.(任务8) 线程2.(任务8) 线程1.(任务9) 线程2.(任务9) 线程3.(任务1) 线程3.(任务2) 线程3.(任务3) 线程3.(任务4) 线程3.(任务5) 线程3.(任务6) 线程3.(任务7) 线程3.(任务8) 线程3.(任务9),执行结果,9.4 线

44、程池,【例9.5】线程池与ManualResetEvent对象结合使用实例。本例将对5到10间的10个随机产生的整数求阶乘(n!),分别用10个线程来完成这些任务。程序中,用ManualResetEvent对象的信号来标记计算任务是否完成,当所有的计算任务都完成后才显示计算结果。public class Facpublic int N get return _n; private int _n;public int FacOfN get return _facOfN; private int _facOfN;private ManualResetEvent _doneEvent;public

45、Fac(int n, ManualResetEvent doneEvent)_n = n;_doneEvent = doneEvent;,9.4 线程池,public void ThreadPoolCallback(Object threadContext)int threadIndex = (int)threadContext;Console.WriteLine(“thread 0 started.“, threadIndex);_facOfN = Calculate(_n);Console.WriteLine(“thread 0 result calculated.“, threadInd

46、ex);_doneEvent.Set(); / 将_doneEvent设置为有信号状态public int Calculate(int n) /计算阶乘(n!)if (n = 0) | (n = 1)return 1;return n * Calculate(n - 1);/end public class Fac,9.4 线程池,static void Main(string args) const int FacCalculations = 10;ManualResetEvent doneEvents = new ManualResetEventFacCalculations;Fac fa

47、cArray = new FacFacCalculations;Random r = new Random();Console.WriteLine(“launching 0 tasks.“, FacCalculations);for (int i = 0; i FacCalculations; i+) /创建ManualResetEvent对象,并初始化为无信号状态 doneEventsi = new ManualResetEvent(false); Fac f = new Fac(r.Next(5, 10), doneEventsi);facArrayi = f;ThreadPool.Que

48、ueUserWorkItem(f.ThreadPoolCallback, i); /等待知道doneEvents中的各个ManualResetEvent对象均有信号为止(所有计算任务均完成) WaitHandle.WaitAll(doneEvents); Console.WriteLine(“All calculations are complete.“);/显示结果for (int i = 0; i FacCalculations; i+) Fac f = facArrayi;Console.WriteLine(“Fac(0) = 1“, f.N, f.FacOfN);Console.Rea

49、dLine();,9.4 线程池,已经成功运行10个线程,并得到有效控制,结果是正确的。,9.5 线程对控件的访问,在多线程编程设计中,不允许一个线程访问在另外一个线程中创建的对象,这是一个基本的原则。但在许多时候,我们恰恰需要这么做,例如一个线程需要访问其主线程中的控件(以显示数据或获得数据等),那我们应该怎么办呢?Control类提供的Invoke()方法可以帮助我们解决这个问题。,Invoke()方法可以调用窗体界面线程(主线程)中的任何一个委托对象,其原型如下:object Control.Invoke(Delegate method) object Control.Invoke(Delegate method, params object args)参数method用于传递已创建的委托对象,该对象关联的方法的参数值则放在数组args中。如果关联的方法没有参数,则使用第一个Invoke()方法。,

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 企业管理 > 管理学资料

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报