第六章-异步与多线程编程_第1页
第六章-异步与多线程编程_第2页
第六章-异步与多线程编程_第3页
第六章-异步与多线程编程_第4页
第六章-异步与多线程编程_第5页
已阅读5页,还剩29页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

.net课程系列C#高级编程.NET方向

第二学期课程第六章异步与多线程编程Newture新程教育异步编程进程与线程BeginInvoke和EndInvokeIAsyncResult接口和AsyncResult类异步编程的4种方法多线程编程Thread类使用线程池线程同步死锁本章目录平时我们会用到打印、下载等操作,这类型的操作比较费时,在程序中调用这类比较费时的代码时,调用方如果停在那里等待费时的代码执行完毕,无疑会严重影响程序的可操作性。现在很多应用程序将设置保存在配置文件中,那么当程序启动时由于需要加载配置,然后利用这些配置数据进行一系列的初始化操作,但因为I/O读取操作稍慢,这将导致程序的主窗体不能立刻显示,给用户一种启动过程十分漫长的感觉,用户体验不好。对于这类问题我们怎么去解决?这类问题,可以借助异步调用或者多线程编程模型轻松解决。引言如下是可能的解决方案:把整个初始化处理放进一个单独线程,主线程启动此线程后继续执行其他操作。例如窗体绘制操作,当初始化配置数据的进行还在执行的同时,主窗体也快速的展现在用户眼前。虽然当前主窗体可能还不能完全可用,但给用户一种程序飞快运行的感觉。配置信息初始化线程此刻也在同步执行,将配置文件中的数据读取到内存,并根据配置对当前程序进行初始化。这就是本章将要讨论的异步编程和多线程编程。引言什么是进程?在启动一个应用程序后,系统将会给它分配一定的内存以及其他的一些资源,这些划定的内存以及资源的物理分隔叫做进程。在Windows系统中,可以通过“任务管理器”来查看当前运行的进程。可以看出,每个进程都包含一定数量的线程,例如360Tray.exe有53个线程。什么是线程?线程是系统分配处理器时间资源的基本单元,或者说是进程之内的独立执行的一个单元,对于操作系统而言,其调度单元是线程。一个进程至少包括一个线程,通常将该线程称为主线程。从另一个角度来说,线程是由进程创建的,由处理器使用的一个执行序列。进程与线程仔细观察“任务管理器”,会发现“应用程序”和“进程”分属两个不同的选项卡,这说明它们是不同的。一个应用程序可能包含一个或多个进程,每个进程都拥有自己独立的数据、执行代码以及系统资源。进程与线程理解进程和线程是进行异步编程的基础。我们之前使用的都是同步编程,什么是同步编程?同步编程指从第一条语句直到最后一条语句都是顺序执行。同步编程是有缺陷的,改进的方式,就是将同步编程改为异步编程,什么是异步编程?异步编程就是合理地利用多线程处理,从理论上讲,这些线程是“同时”执行的。进程与线程在C#中使用线程的方法很多,使用委托的BeginInvoke和EndInvoke方法就是其中之一。BeginInvoke方法可以使用线程异步地执行委托所指向的方法。(委托所代理的目标方法只能为1个)然后通过EndInvoke方法获得方法的返回值(EndInvoke方法的返回值就是被调用方法的返回值),或是确定方法已经被成功调用。BeginInvoke和EndInvokeclassProgram

{publicdelegatevoidPrintDelegate(stringcontent);staticvoidMain(string[]args)

{PrintDelegateprintDelegate=Program.Print;IAsyncResultresult=printDelegate.BeginInvoke("helloworld",null,null);printDelegate.EndInvoke(result);

}publicstaticvoidPrint(stringcontent)

{Console.WriteLine("打印中……\n"+content);System.Threading.Thread.Sleep(2000);

}

}IAsyncResult接口定义异步操作的状态,BeginInvoke方法的返回类型以及EndInvoke方法的参数均为IAsyncResult接口,以下是源代码:IAsyncResult接口和AsyncResult类publicinterfaceIAsyncResult

{objectAsyncState{get;}WaitHandleAsyncWaitHandle{get;}boolCompletedSynchronously{get;}boolIsCompleted{get;}

}属性返回类型说明AsyncStateobject返回一个对象,是启动异步操作的方法的最后一个参数AsyncWaitHandleWaitHandle获取用于等待异步操作完成的WaitHandleCompletedSynchronouslybool获取一个值,指示异步操作是否同步完成IsCompletedbool获取一个值,指示异步操作是否已完成AsyncResult类实现了IAsyncResult接口,主要作用是封装了通过BeginInvoke进行异步调用的执行结果。

BeginInvoke并非直接返回AsyncResult类型,而是返回IAsyncResult接口,但返回值可以显式地转换为AsyncResult类型。AsyncResult类有一个属性AsyncDelegate,该属性保存的是在其上进行异步调用的委托对象,只不过该属性返回的是object类型,需要显式转换为具体的委托类型。IAsyncResult接口和AsyncResult类第一种:使用EndInvoke当使用BeginInvoke异步调用方法时,如果方法未执行完,

EndInvoke返回就会一直阻塞,直到被调用的方法执行完毕。异步编程的4种方法classProgram

{publicdelegatevoidPrintDelegate(stringcontent);staticvoidMain(string[]args)

{intthreadId=Thread.CurrentThread.ManagedThreadId;PrintDelegateprintDelegate=Program.Print;Console.WriteLine("主线程Id:"+threadId+"\t"+"打印方法开始调用");IAsyncResultresult=printDelegate.BeginInvoke("helloworld",null,null);

printDelegate.EndInvoke(result);

}publicstaticvoidPrint(stringcontent)

{intthreadId=Thread.CurrentThread.ManagedThreadId;Console.WriteLine("当前线程Id:"+threadId);Console.WriteLine("打印中……\n"+content);Thread.Sleep(2000);Console.WriteLine("当前线程Id:"+threadId+"\t"+"打印方法调用完毕");

}

}第二种:使用WaitHandle使用WaitHandle类型的WaitOne方法,

WaitOne有5个重载:boolWaitOne()boolWaitOne(intmillisecondsTimeout)boolWaitOne(TimeSpantimeout)boolWaitOne(intmillisecondsTimeout,boolexitContext)boolWaitOne(TimeSpantimeout,boolexitContext)其中,第一个不带任何参数的重载实际是对WaitOne(-1,false)方法的调用第一个参数表示等待的毫秒数,值-1表示无期限等待。第二个参数表示在等待前是否退出上下文的同步域,并在稍后重新获取。第4个和第5个重载实际相同,第5个重载会将TimeSpan类型转换为毫秒数。这些重载的核心实现为第4个重载,其他的都在其基础上实现。异步编程的4种方法异步编程的4种方法classProgram

{publicdelegatevoidPrintDelegate(stringcontent);staticvoidMain(string[]args)

{intthreadId=Thread.CurrentThread.ManagedThreadId;PrintDelegateprintDelegate=Program.Print;Console.WriteLine("主线程Id:"+threadId+"\t"+"打印方法开始调用");IAsyncResultresult=printDelegate.BeginInvoke("helloworld",null,null);

result.AsyncWaitHandle.WaitOne(5000,false);

}}第三种:轮询之前两种方法中,只能等待异步方法执行完毕,在完毕之前没有任何提示信息,整个程序就像没有反应一样,用户体验不好。可以通过IAsyncResult类的的IsCompleted属性来检查异步调用是否完成了,如果没有完成,则可以适时地显示一些提示信息,提升用户体验。异步编程的4种方法classProgram

{publicdelegatevoidPrintDelegate(stringcontent);staticvoidMain(string[]args)

{intthreadId=Thread.CurrentThread.ManagedThreadId;PrintDelegateprintDelegate=Program.Print;Console.WriteLine("主线程Id:"+threadId+"\t"+"打印方法开始调用");IAsyncResultresult=printDelegate.BeginInvoke("helloworld",null,null);

while(!result.IsCompleted)

{Console.WriteLine(".");Thread.Sleep(500);

}

}}第四种:回调之前三种方法都是在等待异步方法执行完毕后才能拿到执行的结果,期间主线程均处于等待状态。回调和它们最大的区别是,在调用BeginInvoke时只要提供了回调方法,那么主线程就不必再等待异步线程工作完毕,异步线程在工作结束后会主动调用提供的回调方法,并在回调方法中做相应的处理,例如显示异步调用的结果,等等。

回顾:IAsyncResultresult=printDelegate.BeginInvoke("helloworld",null,null);第一个参数表示委托签名中的参数第二个参数AsyncCallback,表示回调方法第三个参数object@object,给回调方法传递一些值,一般可以传递被调用方法的委托。异步编程的4种方法异步编程的4种方法classProgram{publicdelegatevoidPrintDelegate(stringcontent);staticvoidMain(string[]args)

{intthreadId=Thread.CurrentThread.ManagedThreadId;PrintDelegateprintDelegate=Program.Print;Console.WriteLine("主线程Id:"+threadId+"\t"+"打印方法开始调用");

IAsyncResultresult=printDelegate.BeginInvoke("helloworld",PrintComplete,printDelegate);

Thread.Sleep(10000);

}publicstaticvoidPrintComplete(IAsyncResultresult)

{intthreadId=Thread.CurrentThread.ManagedThreadId;(result.AsyncStateasPrintDelegate).EndInvoke(result);Console.WriteLine("当前线程Id:"+threadId+"\t"+"打印方法调用完毕")

}}多线程编程的好处前面我们已经提到过,本节开始讲解多线程的编程,例如Thread类的使用、线程池、锁等等。但是多线程编程也有不利的地方,也不能滥用:线程也是对象,也需要占用内存,因此线程越多占用内存也就越多。多线程需要协调和管理,所以需要CPU时间跟踪线程。线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题。线程太多会导致控制太复杂,最终可能带来产生bug的风险。多线程编程在.NET中使用Thread类来创建一个线程,它位于System.Threading命名空间。Thread类Thread类的构造函数:publicThread(ParameterizedThreadStartstart)publicThread(ThreadStartstart)publicThread(ParameterizedThreadStartstart,intmaxStackSize)publicThread(ThreadStartstart,intmaxStackSize)Thread类构造函数涉及三种类型的参数:ParameterizedThreadStart:是一个委托类型,表示此线程开始执行时要调用的方法,支持向调用的方法传递一个参数,该委托的签名如下:

publicdelegatevoidParameterizedThreadStart(objectobj);Thread类Thread类构造函数涉及三种类型的参数:ThreadStart:是一个委托类型,表示此线程开始执行时要调用的方法没有参数的委托类型,不支持向调用的方法传递一个参数,签名如下:

publicdelegatevoidThreadStart();maxStackSize:表示线程要使用的最大堆栈大小。需要注意两点:Thread类的实例只有调用Start方法线程才可以执行使用ThreadStart和ParameterizedThreadStart两个委托时,使用的也分别是两个不同的Start方法重载。Thread类classProgram

{staticvoidMain(string[]args)

{Console.WriteLine("[主线程:{0}]",Thread.CurrentThread.ManagedThreadId);Threadthread1=newThread(Print);thread1.Start();Threadthread2=newThread(PrintEx);thread2.Start("测试打印");

}publicstaticvoidPrint()

{intthreadId=Thread.CurrentThread.ManagedThreadId;Console.WriteLine("[当前线程id]:{0}\t{1}",threadId,DateTime.Now.ToShortTimeString());

}publicstaticvoidPrintEx(objectcontent)

{intthreadId=Thread.CurrentThread.ManagedThreadId;Console.WriteLine("[当前线程id]:{0}\t{1}",threadId,content);

}

}Thread类的属性和方法:Thread类属性CurrentThread:获取当前正在运行的线程IsAlive:指示当前线程的执行状态Name:获取或设置线程的名称Priority:获取或设置线程的优先级CurrentContext:获取线程其中执行的当前上下文IsBackground:指示线程是否为后台线程ThreadState:获取或设置线程的当前状态方法Sleep():将当前线程阻塞指定的毫秒数Abort():终止线程Join():阻塞调用线程,直到某个线程终止时为止Resume():继续已挂起的线程Start()

:启动线程Suspend():挂起线程Interrupt():中断处于WaitSleepJoin线程状态的线程ResetAbort():取消为当前线程请求的Abort线程从创建到结束共分5个状态:初始状态:线程刚被创建就绪状态:线程被启动时运行状态:对于就绪状态的线程,当时间片轮到该线程执行时阻塞状态:死亡状态:线程执行完毕Thread类线程是根据其优先级来调度的,每个线程都有特定的优先级,每个线程在创建时其优先级为:ThreadPriority.NormalThread类在面向对象编程中,创建和销毁对象很费时,因为创建一个对象要获取内存资源或者其他更多资源。创建线程也是如此,因为Thead也是一个对象。使用一种叫做“池(Pool)”的技术可以优化对象的使用,其原理是预先创建一定数量的对象放在“池”里,在对象被请求时,某个对象被从“池”中取出来供使用,使用完毕再重新放回“池”中,等待下一次请求。使用这种机制尽可能地减少了创建和销毁对象的次数,对于很耗资源的对象创建和销毁更加有效。在.NET里面使用“线程池”技术,叫做ThreadPool,位于System.Threading命名空间。需要注意,线程池中的线程均为后台线程,即IsBackground属性为true。意味着在所有前台线程都已退出后,ThreadPool中的线程不会让应用程序继续保持运行。(应用程序必须运行完所有的前台线程才可以退出;而对于后台线程,应用程序则可以不考虑其是否已经运行完毕而直接退出,所有的后台线程在应用程序退出时都会自动结束)使用线程池ThreadPool线程池多用于执行一些简单的、耗时短暂的任务,以下情况不应使用线程池:线程池中的线程均为后台线程,当创建一个前台线程时不应使用线程池。线程池的线程都是默认优先级,当需要创建具有特定优先级的线程时不应使用线程池。当需要某个任务只和特定的线程关联时,不应使用线程池,因为无法选择执行任务时使用的是哪个具体线程。当需要中止特定的线程时不应使用线程池,它不提供这个功能。线程执行时间很长,线程池多用于段而多的线程任务。使用线程池要使用ThreadPool中的线程,需要使用ThreadPool.QueueUserWorkItem指定线程要调用的方法,有两个重载的版本:publicstaticboolQueueUserWorkItem(WaitCallbackcallBack);publicstaticboolQueueUserWorkItem(WaitCallbackcallBack,objectstate);参数WaitCallback是一个委托类型:

publicdelegatevoidWaitCallback(objectstate);两个重载版本的区别在于是否可以传入参数。使用线程池使用线程池classProgram{staticvoidMain(string[]args)

{ThreadPool.QueueUserWorkItem(Counter);ThreadPool.QueueUserWorkItem(Counter,"test");Console.WriteLine("[线程ID={0}]主线程启动",Thread.CurrentThread.ManagedThreadId);Thread.Sleep(500);Console.WriteLine("主线程退出");

}privatestaticvoidCounter(objectstate)

{Console.WriteLine("开始计数,从1到100:");Console.WriteLine("传入的参数值为:{0}",state);

for(intcounter=0;counter<100;counter++)

{

if(null==state) Console.WriteLine("[线程ID={0}]{1}",Thread.CurrentThread.ManagedThreadId,counter);

else Console.WriteLine("[线程ID={0}]{1}",Thread.CurrentThread.ManagedThreadId,state); Thread.Sleep(100);

}

}}代码的作用是使用500个线程一起调用Program对象的Increase()方法,这个方法执行字段count++10000次,每个线程执行完毕,count都会自增10000,500个线程执行的结果,正常情况下最后的值应该是5000000,那事实是否如此?以下是运行5次的结果:如果线程1读到的count的值是0,而此时线程2已经完成自增操作,但并未更新count字段时,这时线程1自增的结果将和线程2相同,都是1,本来应该是2的。这就解释了为什么计算的结果只会比5000000小,而不可能更大的原因。那怎么解决这个问题了?任何时刻都只运行一个线程执行count++操作,这就是线程同步。线程同步classProgram

{intcount;publicintMax{get;set;}publicvoidIncrease()

{for(inti=0;i<Max;i++)

{ count++; }

}staticvoidMain(string[]args)

{Thread[]threads=newThread[500];Programp=newProgram(){Max=10000};for(inti=0;i<threads.Length;i++)

{threads[i]=newThread(p.Increase);threads[i].Start();

}for(inti=0;i<threads.Length;i++)

{ threads[i].Join();}Console.WriteLine("{0:N0}",p.count);

}

}使用lock语句为某个代码块加锁,在加锁的代码块只允许一个线程进入并执行,同时该线程获得该锁,在代码块执行完毕将自动解锁。Lock语句的语法形式:

Objectthislock=newObject();

lock(thislock)

{

//代码块

}这里的thislock对象应该避免使用public访问级别,最佳做法是使用private访问级别。除了实例对象,也可以使用静态对象来保护所有实例共享的数据。线程同步publicvoidIncrease()

{for(inti=0;i<Max;i++)

{

lock(this)

{count++;

}

}

}

privateobject_lock=newobject();

publicvoidIncrease()

{for(inti=0;i<Max;i++)

{

lock(_lock)

{count++;

}

}

}Lock语句其实是Monitor类的快捷语法,lock语句转换成两个方法的调用:Monitor.Enter(Object):获取锁Monitor.Exit():释放锁线程同步

privateobject_lock=newobject();publicvoidIncrease()

{for(inti=0;i<Max;i++)

{Monitor.Enter(_lock);try

{count++;

}finally

{Monitor.Exit(_lock);

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论