在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
【基础篇】
【WinForm多线程编程篇】
【线程池】
【同步】
基础篇 怎样创建一个线程 方法一:使用Thread类 public static void Main(string[] args) 方法二:使用Delegate.BeginInvoke delegate double CalculateMethod(double r);//声明一个委托,表明需要在子线程上执行的方法的函数签名 static void Main(string[] args) public static double Calculate(double r) 方法三:使用ThreadPool.QueueworkItem 受托管的线程与Windows线程 .NET应用的线程实际上仍然是Windows线程。但是,当某个线程被CLR所知时,我们将它称为受托管的线程。具体来说,由受托管的代码创建出来的线程就是受托管的线程。不过,一旦该线程执行了受托管的代码它就变成了受托管的线程。 一个受托管的线程和非受托管的线程的区别在于,CLR将创建一个System.Threading.Thread类的实例来代表并操作前者。在内部实现中,CLR将一个包含了所有受托管线程的列表保存在一个叫做ThreadStore地方。 CLR确保每一个受托管的线程在任意时刻都在一个AppDomain中执行,但是这并不代表一个线程将永远处在一个AppDomain中,它可以随着时间的推移转到其他的AppDomain中。 前台线程与后台线程 启动了多个线程的程序在关闭的时候却出现了问题,如果程序退出的时候不关闭线程,那么线程就会一直的存在,但是大多启动的线程都是局部变量,不能一一的关闭,如果调用Thread.CurrentThread.Abort()方法关闭主线程的话,就会出现ThreadAbortException异常。可以通过这个方法:Thread.IsBackground设置线程为后台线程。 msdn对前台线程和后台线程的解释:托管线程或者是后台线程,或者是前台线程。后台线程不会是托管执行环境处于活动状态,除此之外,后台线程与前台线程是一样的。一旦所有前台进程在托管进程(其中.exe文件时托管程序集)中被停止,系统将停止所有后台线程并关闭。通过设置Thread.IsBackground属性,可以将一个线程指定为后台线程或者前台线程。从非托管代码进入托管执行环境的所有线程都被标记为后台线程。通过创建并启动新的Thread对象而生成的所有线程都是前台线程。 名为BeginXXX和EndXXX的方法是做什么用的 这是.net的一个异步方法名称规范。 .net在设计的时候为异步编程设计了一个异步编程模型(APM),比如所有的Stream就是BeginRead,EndRead,Socket,WebRequet,SqlCommand都运用到了这个模式,一般来讲,调用BeginXXX的时候,一般会启动一个异步过程去执行一个操作,EndInvoke可以接受这个异步操作的返回,当然如果异步操作在EndIncoke调用的时候还没有执行完成,EndInvoke会一直等待异步操作完成或者超时。 .NET的异步编程模型(APM)一般包含BeginXXX,EndXXX,IAsyncResult这三个元素,BeginXXX方法都有返回一个IAsyncResult,而EndXXX都需要接受一个IAsyncResult作为参数。 异步和多线程 异步有许多种方法,我们可以用进程来做异步,或者使用线程,或者硬件的一些特性,比如在实现异步IO的时候,可以以下两种方案: 方案一:可以通过初始化一个子线程,然后在子线程里进行IO,而让主线程顺利往下执行,当子线程执行完毕就回调 方案二:使用硬件的支持(现在许多硬件都有自己的处理器),来实现完全的异步,这时我们只需将IO请求告知硬件驱动程序,然后迅速返回,然后等着硬件IO就绪通知我们就可以了 WinForm多线程编程篇 多线程WinForm程序总是抛出InvalidOperationException,怎么解决 在WinForm中使用线程时,常常遇到一个问题,当在子线程(非UI线程)中修改一个空间的值:比如修改进度条进度,时会抛出异常。 解决方法就是利用控件提供的Invoke和BeginInvoke把调用封送回UI线程,也就是让控件属性修改在UI线程上执行。 例如: delegate void changeText(double result); 这里用到了Control的一个属性InvokeRequired(这个属性石可以在其它线程里访问),这个属性表明调用是否来自非UI线程,如果是,使用BeginInvoke来调用这个函数,否则就直接调用,省去线程封送的过程。 Invoke和BeginInvoke干什么用的,内部是怎么实现的 这两个方法主要是让给出的方法在控件创建的线程上执行。 Invoke使用了Win32API的SendMessage BeginInvoke使用了Win32API的PostMessage 这两个方法想UI线程的消息队列中放入一个消息,当UI线程处理这个消息时,就会在自己的上下文中执行传入的方法,换句话说,凡是使用BeginInvoke和Invoke调用的线程都是在UI主线程中执行,所以如果这些方法里涉及一些静态变量,不用考虑加锁的问题。 每个线程都有消息队列吗? 不是,知识创建了窗体对象的线程才会有消息队列(下面是《Windows核心编程》关于这一段的描述) 当一个线程第一希被建立时,系统假定线程不会被用于任何与用户相关的任务。这样可以减少线程对系统资源的要求。但是,一旦这个线程调用一个与图形用户界面有关的函数(例如检查它的消息队列或建立一个窗口),系统就会为该线程分配一些另外的资源,以便它能够执行与用户界面有关的任务。特别是,系统分配一个THREADINFO结构,并将这个数据结构与线程联系起来。 这个THREADINFO结构包含一组成员变量,利用这组成员,线程可以认为它是在自己独占的环境中运行。THREADINFO是一个内部的、未公开的数据结构,用来指定线程的登记消息队列(posted-message queue)、发送消息队列(send-message queue)、应答消息队列(reply-message queue)、虚拟输入队列(virtualized-input queue)、唤醒标志(wake flag)以及用来描述线程局部输入状态的若干变量。 为什么WinForm不允许跨线程修改UI线程控件的值 vs2005及以上版本,当在Visual Studio调试器中运行代码时,如果您从一个线程访问某个UI元素,而该线程不是创建该UI元素时所在的线程,则会引发InvalidOperationException调试器引发该异常以警告您存在危险的编程操作。UI元素不是线程安全的,所以只应在创建它们的线程上进行访问。 有没有什么办法可以简化WinForm多线程的开发 使用backgroundworker,使用这个组件可以避免回调时的Invoke和BeginInvoke,并且提供了许多丰富的方法和事件 线程池 线程池的作用是什么 减小线程创建和销毁的开销 创建线程涉及到用户模式和内核模式的切换,内存分配,dll通知等一系列过程,线程销毁的步骤也是开销很大的,所以如果应用程序使用完一个线程,我们能把线程暂时存放起来,以备下次使用,就可以减小这些开销。 所有进程使用一个共享的线程池,还是每个进程使用独立的线程池 每个进程都有一个线程池,一个进程中只能有一个实例,它在各个应用程序域(AppDomain)是共享的,线程池仅仅保留相当少的线程,保留的线程可以用SetMinThread这个方法设置,当程序需要一个线程时,线程池中没有空闲的线程时,线程池就会负责创建这个线程,调用完后,不会立即销毁,而是把它放在池子里,以备下次使用,但是,如果超出一定时间没使用,线程池就会回收线程,所以线程池里存在的线程数实际是个动态的过程。 线程池中线程的分类 线程池里的线程按照公用被分成了两大类:工作线程和IO线程(IO完成线程),前者用于执行普通操作,后者专用于异步IO。它们分别在什么情况下被使用,二者工作原理有什么不同?通过下面这个例子,我们用一个流读出一个很大的文件(文件大,操作时间长,便于观察),然后用另一个输出流把所读出的文件的一部分写到磁盘上。 用两种方法创建输出流,分别是: 创建一个异步的流(注意构造函数最后那个true) 创建一个同步流 string readPath = "d:\\工作常用软件\\VS2012Documentation.iso"; public static void ShowThreadDetail(string caller) 输出结果 异步流 Worker:1023; IO:1000 Worker:1023; IO:999 同步流 Worker:1023; IO:1000 Worker:1022; IO:1000 这两个构造函数创建的流都可以使用BeginWrite来异步写数据,但二者行为不同,当使用同步的流进行异步写时,通过回调的输出我们可以看到,它使用的是工作线程,而非IO线程,而异步流使用IO线程。 .NET线程池有什么不足 没有提供方法控制加入线程池的线程:一旦加入线程池,我们没办法挂起,终止这些线程,唯一可以做的就是等他自己执行
同步 CLR怎样实现lock(obj)锁定 从原理上讲,lock和Syncronized Attribute都是用Moniter.Enter实现的,例如: object obj = new object(); lock(obj){ //do things... } 在编译时,会被编译为类似 try{ Moniter.Enter(obj){ //do things... } } catch{...} finally{ Moniter.Exit(obj); } 每个对象实例头部都有一个指针,这个指针指向的结构包含了对象的锁定信息,当第一次使用Moniter.Enter(obj)是,这个obj对象的锁定结构就会被初始化,第二次调用时,会检验这个object的锁定结构,如果锁没有被释放,则调用会阻塞。 互斥对象(Mutex)、事件(Event)对象与lock语句的比较 这里所谓的事件是一种用于同步的内核机制,互斥对象和事件对象属于内核对象,利用内核对象进行线程同步,线程必须要在用户模式和内核模式间切换,所有一般效率很低,但利用互斥对象和事件对象这样的内核对象,可以在多个线程中的各个线程间进行同步。 lock或者Moniter是.net用于一种特殊结构实现的,不涉及模式切换,就是工作在用户方式下,同步速度较快,但是不能跨进程同步。
|
请发表评论