线程池和异步线程
Posted zhchoutai
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程池和异步线程相关的知识,希望对你有一定的参考价值。
线程池和异步线程
文件夹:
- 1 什么是CLR线程池?
- 2 简介下线程池各个长处的实现细节
- 3 线程池ThreadPool的经常用法介绍
- 4 简单理解下异步线程
- 5 异步线程的工作过程和几个重要的元素
- 6 有必要简介下Classic Async Pattern 和Event-based Async Pattern
- 7 异步线程的发展趋势以及.net4.5异步的简化
- 8 本章演示样例
- 自己定义一个简单的线程池
- Asp.net异步IHttpAsyncHandler演示样例
- 9 本章总结
在上一章中通过Thread对象创建我们所须要的线程,可是创建线程的开销是非常大的。在须要以性能为重的项目中这的确easy导致一些性能问题,
事实上我们所想象中的线程开销最好例如以下表示:
1 尽量少的创建线程而且能将线程重复利用 2 最好不要销毁而是挂起线程达到避免性能损失 3 通过一个技术达到让应用程序一个个运行工作,类似于一个队列 4 假设某一线程长时间挂起而不工作的话,须要彻底销毁而且释放资源 5 假设线程不够用的话可以创建线程,而且用户可以自己定制最大线程创建的数量 |
令人欣慰的是微软早就想到了以上几点。于是CLR线程池的概念出现了,说究竟线程池就是一个帮助我们开发者实现多线程的一个方案,就是
用来存放“线程”的对象池,利用线程池我们能够开发出性能比較高的对于多线程的应用,同一时候减低一些不必要的性能损耗。我们不必去手动创建
线程,线程池依据给定线程池中的任务队列的队列速度和相关任务运行速度相比較去自己加入或复用线程。关于线程池的细节我会在下文中具体阐述
让我们依据上节中线程池已经实现了5个长处来具体介绍下线程池的功能
1 尽量少的创建线程而且能将线程重复利用
初始化的线程池中是没有线程的。当应用程序区请求线程池时,线程池会制造一个初始线程。普通情况下,线程池会反复使用这个线程来经量少的创
建线程,这样线程池就能尽量避免去创建新的线程而降低的创建线程的开销
2 最好不要销毁而是挂起线程达到避免性能损失
当一个线程池中的线程工作完成之后。该线程不会被销毁而是被挂起操作等待。关于线程的挂起大家能够參考第一篇。假设应用程序重新请求线程
池的话,那么这个线程会又一次被唤醒,从而是实现了线程的复用而且避免一定的性能损失
3 通过一个技术达到让应用程序一个个运行工作,类似于一个队列
多个应用程序请求线程池后,线程池会将各个应用程序排队处理,首先利用线程池中的一个线程对各个应用程序进行操作,假设应用程序的运行速度
超过了队列的排队速度时。线程池会去创建一个新的线程。否则复用原来的线程
4 假设某一线程长时间挂起而不工作的话,须要彻底销毁而且释放资源
有可能在多个程序请求线程池运行后。线程池中产生了很多挂起的线程,而且这些线程池中的线程会一直处于空暇状态间接导致的内存的浪费,所以微软
为线程池设定了一个超时时间。当挂起的线程超时之后会自己主动销毁这些线程
5 假设线程不够用的话可以创建线程
前面已经提到过,有时候排在队列中的当中一个或多个应用程序工作时间超过了规定的每一个应用程序的排队时间,那么线程池不会坐视无论,线程池会创建
一个新的线程来帮助还有一个须要运行的应用程序
相信大家看完上述5个长处及其细节后,对线程池的目的和长处就豁然开朗了
个人觉得CLR线程池最牛的地方就是它可以依据队列中的应用程序运行时间和各个排队应用程序间的 排队速度进行比較,从而决定是不是创建或者复用原先的线程,假如一系列的应用程序很的简单 或者运行速度非常快的情况下,根本无需创建新的线程,从而这个单一线程能够悠闲的挂起等待排队 的下一个应用程序。 假设应用程序很复杂或者层次不齐。那么正好相反。因为这个线程正在忙。 所以无暇对排队的下个任务进行处理,所以须要创建一个新的线程处理,这样陆陆续续会创建一些 新的线程来完毕队列中的应用程序。假设在运行过程中多余线程会超时自己主动回收,并且CLR线程 池同意用户自己定义加入最大线程数和最小线程数,可是出于性能的考虑微软不建议开发者手动更 改线程池中的线程数量。对于以上几点大家务必理解 |
假设您理解了线程池目的及长处后,让我们温故下线程池的经常使用的几个方法:
1. public static Boolean QueueUserWorkItem(WaitCallback wc, Object state);
WaitCallback回调函数就是前文所阐述的应用程序,通过将一些回调函数放入线程池中让其形成队列,然后线程池会自己主动创建或者复用线程
去运行处理这些回调函数,
State: 这个參数也是很重要的,当运行带有參数的回调函数时。该參数会将引用传入,回调方法中。供其使用
3. public static bool SetMaxThreads(int workerThreads,int completionPortThreads);
4. public static bool SetMinThreads(int workerThreads,int completionPortThreads);
3和4方法 CLR线程池类中预留的两个可以更改,线程池中的工作线程和I/O线程数量的方法。
使用该方法时有两点必须注意:
1.不能将辅助线程的数目或 I/O 完毕线程的数目设置为小于计算机的处理器数目。
2.微软不建议程序猿使用这两个方法的原因是可能会影响到线程池中的性能
我们通过一个简单的样例来温故下
using System; using System.Threading; namespace ThreadPoolApplication { class Program { //设定任务数量 static int count = 5; static void Main(string[] args) { //关于ManualResetEvent大伙不必深究,兴许章将会具体阐述,这里因为如果 //让线程池运行5个任务所以也为每一个任务加上这个对象保持同步 ManualResetEvent[] events=new ManualResetEvent[count]; Console.WriteLine("当前主线程id:{0}",Thread.CurrentThread.ManagedThreadId); //循环每一个任务 for (int i = 0; i < count; i++) { //实例化同步工具 events[i]=new ManualResetEvent(false); //Test在这里就是任务类。将同步工具的引用传入能保证共享区内每次仅仅有一个线程进入 Test tst = new Test(events[i]); Thread.Sleep(1000); //将任务放入线程池中,让线程池中的线程运行该任务 ThreadPool.QueueUserWorkItem(tst.DisplayNumber, new { num1=2}); } //注意这里,设定WaitAll是为了堵塞调用线程(主线程)。让其余线程先运行完成, //当中每一个任务完成后调用其set()方法(收到信号),当全部 //的任务都收到信号后。运行完成,将控制权再次交回调用线程(这里的主线程) ManualResetEvent.WaitAll(events); Console.ReadKey(); } } public class Test { ManualResetEvent manualEvent; public Test(ManualResetEvent manualEvent) { this.manualEvent = manualEvent; } public void DisplayNumber(object a) { Console.WriteLine("当前运算结果:{0}",((dynamic)a).num1); Console.WriteLine("当前子线程id:{0} 的状态:{1}", Thread.CurrentThread.ManagedThreadId,Thread.CurrentThread.ThreadState); //这里是方法运行时间的模拟,如果凝视该行代码,就能看出线程池的功能了 //Thread.Sleep(30000); //这里是释放共享锁,让其它线程进入 manualEvent.Set(); } } }
运行结果:
从显示结果可以看出线程池仅仅创建了id为9,10,11这3个线程来处理这5个任务。由于每一个任务的运行时间很短,所以线程池
的优势被展现出来了
假设我们去掉DisplayNumber方法中的Thread.Sleep(30000) 的凝视的话,会发现因为任务的运行时间远远超于任务在队列中的
排队时间。所以线程池开启了5个线程来运行任务
在非常多时候比如UI或者IO操作时我们希望将这些非常复杂且耗时比較长的逻辑交给后台线程去处理,而不想影响页面的正常执行,并且
我们希望后台线程可以触发一个回调事件来提示该任务已经完毕,所以基于这样的需求越来越多并且在复杂的逻辑下也难以避免一些多线
程的死锁,所以微软为我们提供了一个属于微软自己的异步线程的概念,上一章提到了多线程和异步的基本概念和差别大家能够去温故下。
线程异步指的是一个调用请求发送给被调用者,而调用者不用等待其结果的返回,一般异步运行的任务都须要比較长的时间, |
相信大家理解的异步的概念后都能对异步的根源有个初步的认识,和线程一样。异步也是针对运行方法而设计的。也就是说当我们运行一个
方法时,使用异步方式能够不阻碍主线程的执行而独立执行,直到执行完成后触发回调事件,注意,.net异步线程也是通过内部线程池建立
的。尽管微软将其封装了起来。可是我们也必须了解下
因为托付是方法的抽象,那么假设托付上能设定异步调用的话,方法也能实现异步,所以本节用异步托付来解释下异步线程的工作过程
前文和前一章节中提到了多线程和异步的差别,对于异步线程来说,这正是体现了其工作方式:
调用者发送一个请求 -> 调用者去做自己的事情 -> 请求会异步运行 -> 运行完成能够利用回调函数告诉调用者(也能够不用) |
在具体说明这几个过程之前,让我们来了解下以下的几个重要的元素
AsyncCallback 托付
事实上这个托付是微软给我们提供的用于异步运行方法体后通知该异步方法已经完毕。AsyncCallBack抽象了全部异步方法运行后回调函数(方法)
,它规定了回调函数(方法)必须拥有一个IAsyncResult的參数而且没有返回值,
IAsyncResult 接口
让我们先来看下msdn上关于它的解释
- IAsyncResult 接口由包括可异步操作的方法的类实现。
它是启动异步操作的方法的返回类型,也是结束异步操作的方法的第三个參数的类型
- 当异步操作完毕时,IAsyncResult 对象也将传递给由 AsyncCallback 托付调用的方法
对于第一条的解释。下面两条代码可以直观的理解:
有时候主线程须要等待异步运行后才干运行,尽管这违背的异步的初衷可是还是能够纳入可能的需求行列,所以假设我们在beginInoke 后立马使用EndInvoke的话。主线程(调用者)会被堵塞,直到异步线程运行完成后在启动运行 |
对于第二条的解释:
结束异步操作时须要使用的回调方法,这里IAsyncResult作为參数被传递进了个这方法。这时IAsyncResult起到了向回调方
法传递信息的作用,关于这点会在后文的异步线程的工作过程中详解下
我们最后再来看下IAsyncResult的几个重要属性
在这里再次强调下IAsyncResult第一个属性AsyncState的作用,就像前面所说,有时我们须要将回调函数的參数传入到回调方法体中,
当然传入入口在BeginInvoke的第二个參数中。在回调函数体中我们能够通过将这个属性类型转换成和BeginInvoke第二个參数一摸
一样的类型后加以使用
关于IAsyncResult最后另一点补充:
假设IAsyncResult本身的功能还不能满足你的须要的话,能够自己定义实现自己的AsyncResult类,但必须实现这个接口 |
理解了以上两个关于异步至关重要的2个元素后。让我们进入一段段代码,在来具体看下异步线程的运行过程
//定义一个托付 public delegate void DoSomething(); static void Main(string[] args) { //1.实例化一个托付,调用者发送一个请求,请求运行该方法体(还未运行) DoSomething doSomething = new DoSomething( () => { Console.WriteLine("假设托付使用beginInvoke的话,这里便是异步方法体"); //4。实现完这种方法体后自己主动触发以下的回调函数方法体 }); //3 。调用者(主线程)去触发异步调用,採用异步的方式请求上面的方法体 IAsyncResult result= doSomething.BeginInvoke( //2.自己定义上面方法体运行后的回调函数 new AsyncCallback ( //5.以下是回调函数方法体 //asyncResult.AsyncState事实上就是AsyncCallback托付中的第二个參数 asyncResult => { doSomething.EndInvoke(asyncResult); Console.WriteLine(asyncResult.AsyncState.ToString()); } ) , "BeginInvoke方法的第二个參数就是传入AsyncCallback中的AsyncResult.AsyncState,我们使用时能够强转成相关类型加以使用"); //DoSomething......调用者(主线程)会去做自己的事情 Console.ReadKey(); }
大家细致看这面这段很easy的代码,为了大家理解方便我特意为异步运行过程加上了特有的凝视和序列号,这种话,大伙能直观初步的理解了异步的运行过程。
让我们依据序列号来说明下:
1. 实例化一个托付。调用者发送一个请求,请求运行该方法体(还未运行) 首先将委实例化而且定义好托付所请求的方法体,可是这个时候方法体是不会执行的 2. 这时候和第一步所相似的是,这里能够将定义好的回调函数AsyncCallback 方法体写入BeginInvoke的第一个參数,将须要传入回调方法体的參数放入第二个參数 3.调用者(主线程)去触发异步调用(运行BeginInvoke方法)。採用异步的方式运行托付中的方法体 4.实现完这种方法体后自己主动触发以下的AsyncCallback中的方法体回调函数(能够设定回调函数为空来表示不须要回调) 5 . 运行回调函数方法体。注意使用托付的 EndInvoke方法结束异步操作,而且输出显示传入异步回调函数的參数 再次强调第五点: (1) 因为使用了回调函数。所以必定异步方法体已经运行过了,所以在回调函数中使用EndInvoke方法是不会堵塞的, (2) 能通过EndInvoke方法获得一些返回结果,比如FileStream.EndRead()可以返回读取的字节数等等 |
6 有必要简介下Classic Async Pattern 和Event-based Async Pattern
首先介绍下Classic Async Pattern:
事实上Classic Async Pattern指的就是我们常见的BeginXXX和EndXXX
IAsyncResult 异步设计模式通过名为 BeginOperationName 和 EndOperationName 的两个方法来实现原同步方法的异步调用
让我们再来回想下.net中的几个的BeginXXX 和EndXXX
Stream中的BeginRead,EndRead,BeginWrite,EndWrite Socket中的BeginReceive。EndReceive HttpWebRequest的BeginGetRequestStream和EndGetRequestStream.... |
再来介绍下Event-based Async Pattern
Event-based Async Pattern 值的是类似于 xxxxxxxAsync() 和 类似于event xxxxxCompleteHander
线程Queue定时器进程池和线程池同步异步