异步编程:使用线程池管理线程。异步编程:使用线程池管理线程,异步线程。

by admin on 2018年9月22日

异步编程:使用线程池管理线程

异步编程:使用线程池管理线程,异步线程

异步编程:使用线程池管理线程

图片 1

 从此图中我们会意识 .NET 与C#
的每个版本发布还是起一个“主题”。即:C#1.0托管代码→C#2.0泛型→C#3.0LINQ→C#4.0动态语言→C#5.0异步编程。现在自家呢流行版本的“异步编程”主题写系列分享,期待您的查阅和点评。

 

现在之应用程序越来越复杂,我们常用采用《异步编程:线程概述及使用》中涉嫌的多线程技术来增长应用程序的响应速度。这时我们一再之创造同销毁线程来受应用程序快速响应操作,这往往之创及销毁的会降低应用程序性能,我们可引入缓存机制解决是题目,此缓存机制亟待解决而:缓存的轻重问题、排队执行任务、调度空闲线程、按需创建新线程及销毁多余空闲线程……如今微软就也咱提供了现的缓存机制:线程池

        
线程池原自于对象池,在详细分解明线程池前为咱先行来打听下何为对象池。

流程图:

 图片 2

 

         对于对象池的清理通常设计片种方式:

1)         手动清理,即积极调用清理的计。

2)         自动清理,即由此System.Threading.Timer来实现定时清理。

 

重在实现代码:

 

  图片 3public
sealed class ObjectPool<T> where T : ICacheObjectProxy<T> {
// 最深容量 private Int32 m_maxPoolCount = 30; // 最小容量 private
Int32 m_minPoolCount = 5; // 已怀容量 private Int32 m_currentCount; //
空闲+被用 对象列表 private Hashtable m_listObjects; // 最酷空闲时间
private int maxIdleTime = 120; // 定时清理对象池目标 private Timer timer
= null; /// <summary> /// 创建对象池 /// </summary> ///
<param name=”maxPoolCount”>最小容量</param> /// <param
name=”minPoolCount”>最老容量</param> /// <param
name=”create_params”>待创建的骨子里目标的参数</param> public
ObjectPool(Int32 maxPoolCount, Int32 minPoolCount, Object[]
create_params){ } /// <summary> /// 获取一个目标实例 ///
</summary> ///
<returns>返回内部实际目标,若返回null则线程池已满</returns>
public T GetOne(){ } /// <summary> /// 释放该目标池 ///
</summary> public void Dispose(){ } /// <summary> ///
将对象池中指定的目标重置并设置也空状态 /// </summary> public
void ReturnOne(T obj){ } /// <summary> /// 手动清理对象池 ///
</summary> public void ManualReleaseObject(){ } ///
<summary> /// 自动清理对象池(对超越 最小容量 的悠闲对象进行自由)
/// </summary> private void AutoReleaseObject(Object obj){ } }
实现的基本点代码

 

由此对“对象池”的一个横认识能支援咱重新快理解线程池。

 

线程池ThreadPool类详解

ThreadPool静态类,为应用程序提供一个是因为系统管理的辅助线程池,从而使你得集中精力于应用程序任务要无是线程管理。每个过程都发出一个线程池,一个Process中只能发出一个实例,它以各个应用程序域(AppDomain)是共享的。

当中,线程池将自己之线程划分工作者线程(辅助线程)和I/O线程。前者用于实施日常的操作,后者专用于异步IO,比如文件及网要,注意,分类并无说明两种植线程本身有差别,内部仍然是同的。

图片 4public
static class ThreadPool { //
将操作系统句柄绑定到System.Threading.ThreadPool。 public static bool
BindHandle(SafeHandle osHandle); //
检索由ThreadPool.GetMaxThreads(Int32,Int32)方法返回的绝深线程池线程数和手上活动线程数之间的差值。
public static void GetAvailableThreads(out int workerThreads , out int
completionPortThreads); //
设置及搜索可以以处于活动状态的线程池请求的数据。 //
所有大于此数量的乞求将保持排队状态,直到线程池线程变为可用。 public
static bool SetMaxThreads(int workerThreads, int completionPortThreads);
public static void GetMaxThreads(out int workerThreads, out int
completionPortThreads); //
设置以及检索线程池以初请求预测中保护的空闲线程数。 public static bool
SetMinThreads(int workerThreads, int completionPortThreads); public
static void GetMinThreads(out int workerThreads, out int
completionPortThreads); //
将艺术排入队列以便执行,并指定包含该办法所用多少的目标。此措施以有线程池线程变得可用时实行。
public static bool QueueUserWorkItem(WaitCallback callBack, object
state); // 将重叠的 I/O 操作排队以便执行。如果成功地以此操作排队到 I/O
完成端口,则也 true;否则也 false。 //
参数overlapped:要排队的System.Threading.NativeOverlapped结构。 public
static bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped);
// 将指定的委托排队到线程池,但非见面拿调用堆栈传播及劳动力线程。 public
static bool UnsafeQueueUserWorkItem(WaitCallback callBack, object
state); // 注册一个等待Threading.WaitHandle的托,并指定一个 32
位有标志整数来表示超时值(以毫秒为单位)。 // executeOnlyOnce如果为
true,表示在调用了委托后,线程将不再以waitObject参数达到等待; // 如果为
false,表示每次就等操作后都重置计时器,直到撤销等。 public static
RegisteredWaitHandle RegisterWaitForSingleObject( WaitHandle waitObject
, WaitOrTimerCallback callBack, object state, Int
millisecondsTimeOutInterval, bool executeOnlyOnce); public static
RegisteredWaitHandle UnsafeRegisterWaitForSingleObject( WaitHandle
waitObject , WaitOrTimerCallback callBack , object state , int
millisecondsTimeOutInterval , bool executeOnlyOnce); …… } ThreadPool

1)         使用GetMaxThreads()和SetMaxThreads()获取与装置极端老线程数

唯独排队到线程池的操作数仅为内存的范围;而线程池限制进程遭到好而且处于活动状态的线程数(默认情况下,限制每个
CPU 可以行使 25 独工作者线程和 1,000 独 I/O 线程(根据机器CPU个数与.net
framework版本的两样,这些数据或许会见有转变)),所有大于此数额的恳求将保障排队状态,直到线程池线程变为可用。

不建议还改线程池中之最好酷线程数:

a)        
将线程池大小设置得极其非常,可能会见招致重复累之推行上下文切换及深化资源的争用情况。

b)        
其实FileStream的异步读写,异步发送接受Web请求,System.Threading.Timer定时器,甚至采用delegate的beginInvoke都见面默认调用
ThreadPool,也就是说不仅你的代码可能采用到线程池,框架中也可能行使到。

c)        
一个应用程序池是一个独门的历程,拥有一个线程池,应用程序池中可出多个WebApplication,每个运行于一个独立的AppDomain中,这些WebApplication公用一个线程池。

 

2)         使用GetMinThreads()和SetMinThreads()获取与装极端小空闲线程数

为避免为线程分配不必要的仓库空间,线程池按照一定之光阴间隔创建新的悠闲线程(该距离也半秒)。所以要是尽小空闲线程数设置的过小,在短期内执行大气职责会以缔造新空闲线程的嵌入延迟导致性瓶颈。最小空闲线程数默认值等于机械及之CPU核数,并且不建议改变最小空闲线程数。

每当启动线程池时,线程池具有一个措延迟,用于启用最小空闲线程数,以加强应用程序的吞吐量。

在线程池运行面临,对于实行完毕任务之线程池线程,不会见即刻销毁,而是返回到线程池,线程池会维护最小之空闲线程数(即使应用程序所有线程都是悠闲状态),以便队列任务可即时启动。超过此最小数目的悠闲线程一段时间没事做后会友善醒来终止自己,以节约系统资源。

3)         静态方法GetAvailableThreads()

经过静态方法GetAvailableThreads()返回的线程池线程的绝深数量及时动数量之间的差值,即获取线程池中即可用的线程数目

4)         两单参数

法GetMaxThreads()、SetMaxThreads()、GetMinThreads()、SetMinThreads()、GetAvailableThreads()钧包含两个参数。参数workerThreads指工作者线程;参数completionPortThreads指异步
I/O 线程。

由此调用 ThreadPool.QueueUserWorkItem 并传递 WaitCallback
委托来使用线程池。也堪经过采用 ThreadPool.RegisterWaitForSingleObject
并传递 WaitHandle(在朝着那个发出信号或过期时,它用抓住针对由于
WaitOrTimerCallback
委托包装的章程的调用)来将和待操作相关的劳作起排队到线程池中。若一旦撤等待操作(即不再实施WaitOrTimerCallback委托),可调用RegisterWaitForSingleObject()方法返回的RegisteredWaitHandle的
Unregister 方法。

倘你明白调用方的堆栈与以排队任务执行中实施之所有平安检查不相干,则还足以动用未安全之方
ThreadPool.UnsafeQueueUserWorkItem 和
ThreadPool.UnsafeRegisterWaitForSingleObject。QueueUserWorkItem 和
RegisterWaitForSingleObject
都见面捕获调用方的库,此堆栈将在线程池线程开始实行任务时合并到线程池线程的堆栈中。如果要进行安全检查,则必须检查全堆栈,但她还存有自然的特性开销。使用“不安全之”方法调用并无见面提供绝对的平安,但她会供更好的性质。

于一个线程不确定地伺机一个水源对象上可用状态,这对准线程的内存资源来说是同等栽浪费。ThreadPool.RegisterWaitForSingleObject()为我们提供了同种办法:在一个基础对象变得可用的时调用一个方。

动需要留意:

1)         WaitOrTimerCallback委托参数,该信托接受一个名叫吧timeOut的Boolean参数。如果 WaitHandle 在指定时间外尚未收受信号(即,超时),则为true,否则也 false。回调方法可因timeOut的价值来针对地采取措施。

2)         名为executeOnlyOnce的Boolean参数。传true则意味线程池线程只实行回调方法一致软;若传false则表示内核对象每次收到信号,线程池线程都见面履行回调方法。等待一个AutoResetEvent对象时,这个力量更加有因此。

3)         RegisterWaitForSingleObject()方法返回一个RegisteredWaitHandle对象的援。这个目标标识了线程池正在其点等待的根本对象。我们得调用它的Unregister(WaitHandle
waitObject)方法取消由RegisterWaitForSingleObject()注册之等待操作(即WaitOrTimerCallback委托不再执行)。Unregister(WaitHandle
waitObject)的WaitHandle参数表示成功取消注册的等候操作后线程池会向这个目标发出信号(set()),若不思接受这通知可以传递null。

         示例:

图片 5private
static void Example_RegisterWaitForSingleObject() { //
加endWaitHandle的来由:如果推行了不久退出办法会促成有东西吃放出,造成排队的职责不克尽,原因还当研
AutoResetEvent endWaitHandle = new AutoResetEvent(false); AutoResetEvent
notificWaitHandle = new AutoResetEvent(false); AutoResetEvent waitHandle
= new AutoResetEvent(false); RegisteredWaitHandle registeredWaitHandle =
ThreadPool.RegisterWaitForSingleObject( waitHandle, (Object state, bool
timedOut) => { if (timedOut)
Console.WriteLine(“RegisterWaitForSingleObject因超时而执行”); else
Console.WriteLine(“RegisterWaitForSingleObject收到WaitHandle信号”); },
null, TimeSpan.FromSeconds(2), true ); //
取消等待操作(即不再实行WaitOrTimerCallback委托)
registeredWaitHandle.Unregister(notificWaitHandle); // 通知
ThreadPool.RegisterWaitForSingleObject( notificWaitHandle, (Object
state, bool timedOut) => { if (timedOut)
Console.WriteLine(“第一独RegisterWaitForSingleObject没有调用Unregister()”);
else
Console.WriteLine(“第一单RegisterWaitForSingleObject调用了Unregister()”);
endWaitHandle.Set(); }, null, TimeSpan.FromSeconds(4), true );
endWaitHandle.WaitOne(); } 示例

履行上下文

        
上等同略带节中说交:线程池最特别线程数设置了大可能会见招Windows频繁执行上下文切换,降低程序性能。对于大部分园友不会见差强人意这样的答,我和你同呢嗜“知其然,再知其所以然”。

.NET中上下文太多,我最终得出的定论是:上下文切换中之上下文专指“执行上下文”。

尽上下文包括:安全达成下文、同步上下文(System.Threading.SynchronizationContext)、逻辑调用上下文(System.Runtime.Messaging.CallContext)。即:安全设置(压缩栈、Thread的Principal属性和Windows身份)、宿主设置(System.Threading.HostExcecutingContextManager)以及逻辑调用上下文数据(System.Runtime.Messaging.CallContext的LogicalSetData()和LogicalGetData()方法)。

当一个“时间片”结束时,如果Windows决定重新调度以及一个线程,那么Windows不见面实行上下文切换。如果Windows调度了一个不等之线程,这时Windows执行线程上下文切换。

        
当Windows上下文切换至外一个线程时,CPU将尽一个异之线程,而前线程的代码和数量还在CPU的高速缓存中,(高速缓存使CPU不自然经常看RAM,RAM的快较CPU高速缓存慢得多),当Windows上下文切换到一个新线程时,这个新线程极有或而履不同的代码并走访不同之数据,这些代码和多少不在CPU的高速缓存中。因此,CPU必须看RAM来填充它的高速缓存,以恢复快实行状态。但是,在那“时间片”执行了晚,一差新的线程上下文切换又来了。

上下文切换所来的开销不见面变来任何内存和总体性及之收入。执行上下文所需要的年月在CPU架构和速(即“时间片”的分红)。而填写充CPU缓存所用的时在系统运转的应用程序、CPU、缓存的高低和任何各种因素。所以,无法也各级一样不成线程上下文切换的时刻支出为闹一个确定的值,甚至无法被有一个量的价值。唯一确定的凡,如果一旦构建大性能的应用程序和零部件,就应尽可能避免线程上下文切换。

除开,执行垃圾回收时,CLR必须挂于(暂停)所有线程,遍历它们的栈来查找根以便对堆放着之对象进行标记,再次遍历它们的堆栈(有的对象在调减期间有了移动,所以要是翻新她的干净),再过来所有线程。所以,减少线程的数码也会见显提升垃圾回收器的性能。每次用一个调试器并撞一个断点,Windows都见面挂于在调试之应用程序中之享有线程,并在单步执行或者运行应用程序时回升所有线程。因此,你用之线程越多,调试体验也尽管一发差。

Windows实际记录了每个线程被齐下文切换到的次数。可以运用如Microsoft
Spy++这样的家伙查看此数量。这个家伙是Visual
Studio附带的一个聊器(vs按安装路径\Visual Studio
2012\Common7\Tools),如图

图片 6

在《异步编程:线程概述及动》中自己干了Thread的鲜独及下文,即:

1)         CurrentContext       
获取线程正在内部实施的即达下文。主要用于线程内部存储数据。

2)         ExecutionContext   
获取一个System.Threading.ExecutionContext对象,该目标涵盖关于当前线程的各种上下文的音。主要用以线程间数共享。

中间赢得到之System.Threading.ExecutionContext就是依照小节要说的“执行上下文”。

图片 7public
sealed class ExecutionContext : IDisposable, ISerializable { public void
Dispose(); public void GetObjectData(SerializationInfo info,
StreamingContext context); //
此方式对将尽上下文从一个线程传播及外一个线程非常有效。 public
ExecutionContext CreateCopy(); // 从即线程捕获执行上下文的一个副本。
public static ExecutionContext Capture(); //
在时线程上的指定执行上下文中运作有方法。 public static void
Run(ExecutionContext executionContext, ContextCallback callback, object
state); // 取消执行上下文在异步线程之间的流淌。 public static
AsyncFlowControl SuppressFlow(); public static bool IsFlowSuppressed();
// RestoreFlow 撤消以前的 SuppressFlow 方法调用的熏陶。 // 此措施由
SuppressFlow 方法返回的 AsyncFlowControl 结构的 Undo 方法调用。 //
应采用 Undo 方法(而非是 RestoreFlow 方法)恢复执行上下文的流动。 public
static void RestoreFlow(); } View
Code

ExecutionContext
类提供的意义让用户代码可以在用户定义之异步点之间捕获和传导此达成下文。公共语言运行时(CLR)确保以托管进程内运行时定义之异步点之间同样地传
ExecutionContext。

在一个线程(初始线程)使用其它一个线程(辅助线程)执行任务时,CLR会将前者的行上下文流向(复制到)辅助线程(注意是活动流向是单方向的)。这便确保了帮扶线程执行之别操作使用的凡千篇一律之平安设置与宿主设置。还担保了初始线程的逻辑调用上下文可以在帮助线程中采取。

不过实践上下文的复制会导致一定之性影响。因为执行上下文中包含大量信息,而采访所有这些信,再把它们复制到援手线程,要耗费成千上万光阴。如果帮线程又用了又多地帮线程,还必须创造和初始化更多之实施上下文数据结构。

据此,为了提升应用程序性能,我们可以阻碍实施上下文的流。当然就只有当帮线程不需还是不看上下文信息之时光才会进行阻挠。

下让有一个示范为了演示:

1)         在线程间共享逻辑调用上下文数据(CallContext)。

2)         为了提升性,阻止\还原执行上下文的流淌。

3)         在手上线程上的指定执行上下文中运行有方法。

图片 8private
static void Example_ExecutionContext() {
CallContext.LogicalSetData(“Name”, “小红”);
Console.WriteLine(“主线程中Name为:{0}”,
CallContext.LogicalGetData(“Name”)); // 1)
在线程间共享逻辑调用上下文数据(CallContext)。
Console.WriteLine(“1)在线程间共享逻辑调用上下文数据(CallContext)。”);
ThreadPool.QueueUserWorkItem((Object obj) =>
Console.WriteLine(“ThreadPool线程中Name为:\”{0}\””,
CallContext.LogicalGetData(“Name”))); Thread.Sleep(500);
Console.WriteLine(); // 2) 为了提升性能,取消\回复执行上下文的流动。
ThreadPool.UnsafeQueueUserWorkItem((Object obj) =>
Console.WriteLine(“ThreadPool线程使用Unsafe异步执行办法来取消执行上下文的流淌。Name为:\”{0}\””
, CallContext.LogicalGetData(“Name”)), null);
Console.WriteLine(“2)为了提升性,取消/恢复执行上下文的流淌。”);
AsyncFlowControl flowControl = ExecutionContext.SuppressFlow();
ThreadPool.QueueUserWorkItem((Object obj) =>
Console.WriteLine(“(取消ExecutionContext流动)ThreadPool线程中Name为:\”{0}\””,
CallContext.LogicalGetData(“Name”))); Thread.Sleep(500); //
恢复不引进使用ExecutionContext.RestoreFlow() flowControl.Undo();
ThreadPool.QueueUserWorkItem((Object obj) =>
Console.WriteLine(“(恢复ExecutionContext流动)ThreadPool线程中Name为:\”{0}\””,
CallContext.LogicalGetData(“Name”))); Thread.Sleep(500);
Console.WriteLine(); // 3)
在现阶段线程上之指定执行上下文中运行有方法。(通过获取调用上下文数据证明)
Console.WriteLine(“3)在当下线程上的指定执行上下文中运作有方法。(通过获得调用上下文数据证实)”);
ExecutionContext curExecutionContext = ExecutionContext.Capture();
ExecutionContext.SuppressFlow(); ThreadPool.QueueUserWorkItem( (Object
obj) => { ExecutionContext innerExecutionContext = obj as
ExecutionContext; ExecutionContext.Run(innerExecutionContext, (Object
state) =>
Console.WriteLine(“ThreadPool线程中Name为:\”{0}\””<br> ,
CallContext.LogicalGetData(“Name”)), null); } , curExecutionContext ); }
View Code

结果使图:

图片 9

 

 

 注意:

1)        
示例中“在时下线程上的指定执行上下文中运作有方法”:代码中得使用ExecutionContext.Capture()获取当前实施上下文的一个副本

a)        
若直接利用Thread.CurrentThread.ExecutionContext则会报“无法利用以下上下文:
跨 AppDomains 封送的上下文、不是经捕获操作获取的上下文或已作 Set
调用的参数的上下文。”错误。

b)        
若使用Thread.CurrentThread.ExecutionContext.CreateCopy()会报“只能复制新近捕获(ExecutionContext.Capture())的上下文”。

2)        
取消执行上下文流动除了使用ExecutionContext.SuppressFlow()方式外。还好由此应用ThreadPool的UnsafeQueueUserWorkItem

UnsafeRegisterWaitForSingleObject来实行委托方。原因是无安全之线程池操作不见面传导压缩堆栈。每当压缩堆栈流动时,托管的中心、同步、区域设置及用户上下文也跟着流动。

 

线程池线程中的良

线程池线程中未处理的酷将终止进程。以下为是规则之老三种植例外情况: 

  1. 鉴于调用了 Abort,线程池线程中以吸引ThreadAbortException。 
    2.
    出于正值卸载应用程序域,线程池线程中拿引发AppDomainUnloadedException。 
  2. 公共语言运行库或宿主进程将停止线程。

何时休使用线程池线程

本大家还曾经知道线程池为咱提供了造福的异步API及托管的线程管理。那么是休是其它时刻都应有使用线程池线程呢?当然不是,我们还是用“因地制宜”的,在偏下几种植情景下,适合给创造并保管好的线程而非是使用线程池线程:

 

 

  本博文介绍线程池以及那个基础对象池,ThreadPool类的以和注意事项,如何排队办事起到线程池,执行上下文及线程上下文传递问题…… 

线程池虽然也咱提供了异步操作的惠及,但是它们不支持对线程池中单个线程的复杂性控制致使我们发来情况下会一直利用Thread。并且它们对“等待”操作、“取消”操作、“延续”任务相当操作比较繁琐,可能驱使你从新造轮子。微软吗想到了,所以于.NET4.0底时刻进入了“并行任务”并以.NET4.5受针对那个进行改良,想了解“并行任务”的园友可以先看《(译)关于Async与Await的FAQ》。

本节到此结束,感谢大家的玩。赞之说话还请求多引进啊 (*^_^*)

 

 

 

 

参考资料:《CLR via C#(第三版)》

 

 摘自:http://www.cnblogs.com/heyuquan/archive/2012/12/23/threadPool-manager.html

 

http://www.bkjia.com/C\_jc/1150546.htmlwww.bkjia.comtruehttp://www.bkjia.com/C\_jc/1150546.htmlTechArticle异步编程:使用线程池管理线程,异步线程
异步编程:使用线程池管理线程 从此图中我们会意识 .NET 与C#
的每个版本发布还是发一个主题…

图片 10

 从此图被我们见面发现 .NET 与C#
的每个版本发布还是起一个“主题”。即:C#1.0托管代码→C#2.0泛型→C#3.0LINQ→C#4.0动态语言→C#5.0异步编程。现在自哉新型版本的“异步编程”主题写系列分享,期待而的查阅和点评。

 

现行的应用程序越来越复杂,我们常常要运用《异步编程:线程概述及动》被涉及的多线程技术来增强应用程序的响应速度。这时我们反复之创始与销毁线程来让应用程序快速响应操作,这往往之创办和销毁的会落应用程序性能,我们可以引入缓存机制解决之题目,此缓存机制亟待缓解而:缓存的大大小小问题、排队执行任务、调度空闲线程、按需创建新线程及销毁多余空闲线程……如今微软已经为我们提供了成的缓存机制:线程池

        
线程池原自于对象池,在详细解释明线程池前给咱先行来了解下何为对象池。

流程图:

 图片 11

 

         对于对象池的清理通常设计片种艺术:

1)         手动清理,即积极调用清理的方法。

2)         自动清理,即透过System.Threading.Timer来实现定时清理。

 

重中之重实现代码:

 

 

图片 12图片 13

public sealed class ObjectPool<T> where T : ICacheObjectProxy<T>
{
    // 最大容量
    private Int32 m_maxPoolCount = 30;
    // 最小容量
    private Int32 m_minPoolCount = 5;
    // 已存容量
    private Int32 m_currentCount;
    // 空闲+被用 对象列表
    private Hashtable m_listObjects;
    // 最大空闲时间
    private int maxIdleTime = 120;
    // 定时清理对象池对象
    private Timer timer = null;

    /// <summary>
    /// 创建对象池
    /// </summary>
    /// <param name="maxPoolCount">最小容量</param>
    /// <param name="minPoolCount">最大容量</param>
    /// <param name="create_params">待创建的实际对象的参数</param>
    public ObjectPool(Int32 maxPoolCount, Int32 minPoolCount, Object[] create_params){ }

    /// <summary>
    /// 获取一个对象实例
    /// </summary>
    /// <returns>返回内部实际对象,若返回null则线程池已满</returns>
    public T GetOne(){ }

    /// <summary>
    /// 释放该对象池
    /// </summary>
    public void Dispose(){ }

    /// <summary>
    /// 将对象池中指定的对象重置并设置为空闲状态
    /// </summary>
    public void ReturnOne(T obj){ }

    /// <summary>
    /// 手动清理对象池
    /// </summary>
    public void ManualReleaseObject(){ }

    /// <summary>
    /// 自动清理对象池(对大于 最小容量 的空闲对象进行释放)
    /// </summary>
    private void AutoReleaseObject(Object obj){ }
}

落实的根本代码

 

通过对“对象池”的一个大致认识能帮咱更快理解线程池。

 

线程池ThreadPool类详解

ThreadPool静态类,为应用程序提供一个出于系统管理的辅助线程池,从而使您可集中精力于应用程序任务要无是线程管理。每个过程都出一个线程池,一个Process中不得不发出一个实例,它当挨家挨户应用程序域(AppDomain)是共享的。

以里头,线程池将团结的线程划分工作者线程(辅助线程)和I/O线程。前者用于实践日常的操作,后者专用于异步IO,比如文件与网络要,注意,分类并无说明两栽线程本身来出入,内部还是是同样的。

图片 14图片 15

public static class ThreadPool
{
    // 将操作系统句柄绑定到System.Threading.ThreadPool。
    public static bool BindHandle(SafeHandle osHandle);

    // 检索由ThreadPool.GetMaxThreads(Int32,Int32)方法返回的最大线程池线程数和当前活动线程数之间的差值。
    public static void GetAvailableThreads(out int workerThreads
            , out int completionPortThreads);

    // 设置和检索可以同时处于活动状态的线程池请求的数目。
    // 所有大于此数目的请求将保持排队状态,直到线程池线程变为可用。
    public static bool SetMaxThreads(int workerThreads, int completionPortThreads);
    public static void GetMaxThreads(out int workerThreads, out int completionPortThreads);
    // 设置和检索线程池在新请求预测中维护的空闲线程数。
    public static bool SetMinThreads(int workerThreads, int completionPortThreads);
    public static void GetMinThreads(out int workerThreads, out int completionPortThreads);

    // 将方法排入队列以便执行,并指定包含该方法所用数据的对象。此方法在有线程池线程变得可用时执行。
    public static bool QueueUserWorkItem(WaitCallback callBack, object state);
    // 将重叠的 I/O 操作排队以便执行。如果成功地将此操作排队到 I/O 完成端口,则为 true;否则为 false。
    // 参数overlapped:要排队的System.Threading.NativeOverlapped结构。
    public static bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped);
    // 将指定的委托排队到线程池,但不会将调用堆栈传播到工作者线程。
    public static bool UnsafeQueueUserWorkItem(WaitCallback callBack, object state);

    // 注册一个等待Threading.WaitHandle的委托,并指定一个 32 位有符号整数来表示超时值(以毫秒为单位)。
    // executeOnlyOnce如果为 true,表示在调用了委托后,线程将不再在waitObject参数上等待;
    // 如果为 false,表示每次完成等待操作后都重置计时器,直到注销等待。
    public static RegisteredWaitHandle RegisterWaitForSingleObject(
            WaitHandle waitObject
            , WaitOrTimerCallback callBack, object state, 
            Int millisecondsTimeOutInterval, bool executeOnlyOnce);
    public static RegisteredWaitHandle UnsafeRegisterWaitForSingleObject(
              WaitHandle waitObject
            , WaitOrTimerCallback callBack
            , object state
            , int millisecondsTimeOutInterval
            , bool executeOnlyOnce);
    ……
}

ThreadPool

  1. 线程池线程数

1)         使用GetMaxThreads()和SetMaxThreads()获取与设置极端深线程数

然而排队到线程池的操作数仅吃内存的范围;而线程池限制进程中好又处于活动状态的线程数(默认情况下,限制每个
CPU 可以采取 25 单工作者线程和 1,000 只 I/O 线程(根据机器CPU个数和.net
framework版本的不等,这些数量也许会见发出浮动)),所有大于此数的要将保持排队状态,直到线程池线程变为可用。

无建议再改线程池中的不过可怜线程数:

a)        
将线程池大小设置得最怪,可能会见招致重复累的履行上下文切换及深化资源的争用情况。

b)        
其实FileStream的异步读写,异步发送接受Web请求,System.Threading.Timer定时器,甚至采取delegate的beginInvoke都见面默认调用
ThreadPool,也就是说不仅你的代码可能利用到线程池,框架内也说不定采用到。

c)        
一个应用程序池是一个独立的历程,拥有一个线程池,应用程序池中可以生多个WebApplication,每个运行于一个独自的AppDomain中,这些WebApplication公用一个线程池。

 

2)         使用GetMinThreads()和SetMinThreads()获取与设置极端小空闲线程数

否避为线程分配不必要之仓库空间,线程池按照一定之岁月间隔创建新的悠闲线程(该距离也半秒)。所以要尽小空闲线程数设置的过小,在短期内执行大气职责会盖缔造新空闲线程的内置延迟导致性瓶颈。最小空闲线程数默认值等于机械及之CPU核数,并且不建议转最小空闲线程数。

每当起步线程池时,线程池具有一个搭延迟,用于启用最小空闲线程数,以加强应用程序的吞吐量。

在线程池运行面临,对于推行了任务的线程池线程,不见面即时销毁,而是回到到线程池,线程池会维护最小的空闲线程数(即使应用程序所有线程都是悠闲状态),以便队列任务可以及时启动。超过这最小数码的闲暇线程一段时间没事做后会见好醒来终止自己,以节约系统资源。

3)         静态方法GetAvailableThreads()

经过静态方法GetAvailableThreads()返回的线程池线程的无限特别数目及目前倒数量之间的差值,即获取线程池中时可用之线程数目

4)         两单参数

方GetMaxThreads()、SetMaxThreads()、GetMinThreads()、SetMinThreads()、GetAvailableThreads()钧包含两单参数。参数workerThreads指工作者线程;参数completionPortThreads指异步
I/O 线程。

  1. 排队办事起

通过调用 ThreadPool.QueueUserWorkItem 并传递 WaitCallback
委托来使用线程池。也得以经应用 ThreadPool.RegisterWaitForSingleObject
并传递 WaitHandle(在朝着那个发出信号或逾期时,它以抓住对由于
WaitOrTimerCallback
委托包装的计的调用)来将跟等待操作相关的工作起排队到线程池中。若使撤等待操作(即不再执行WaitOrTimerCallback委托),可调用RegisterWaitForSingleObject()方法返回的RegisteredWaitHandle的
Unregister 方法。

一旦你知道调用方的库与于排队任务执行中履行的备平安检查无系,则还可使用不安全的计
ThreadPool.UnsafeQueueUserWorkItem 和
ThreadPool.UnsafeRegisterWaitForSingleObject。QueueUserWorkItem 和
RegisterWaitForSingleObject
都见面捕获调用方的库房,此堆栈将在线程池线程开始实践任务时合并到线程池线程的仓库中。如果急需展开安全检查,则必须检查全堆栈,但其还享有一定之习性开销。使用“不安全的”方法调用并无会见供绝对的安,但其见面提供再好之性。

  1. 于一个基本构造可用时调用一个办法

被一个线程不确定地伺机一个水源对象上可用状态,这对线程的内存资源来说是相同种植浪费。ThreadPool.RegisterWaitForSingleObject()为咱提供了平等栽方法:在一个本对象变得可用之时光调用一个艺术。

运用要小心:

1)         WaitOrTimerCallback委托参数,该信托接受一个叫做也timeOut的Boolean参数。如果 WaitHandle 在指定时间内无收取信号(即,超时),则也true,否则也 false。回调方法可以根据timeOut的价来对地采取措施。

2)         名为executeOnlyOnce的Boolean参数。传true则意味着线程池线程只实行回调方法同样不成;若传false则表示内核对象每次接到信号,线程池线程都见面执行回调方法。等待一个AutoResetEvent对象时,这个功效更加有因此。

3)         RegisterWaitForSingleObject()方法返回一个RegisteredWaitHandle对象的援。这个目标标识了线程池正在她上面等待的基石对象。我们得以调用它的Unregister(WaitHandle
waitObject)方法取消由RegisterWaitForSingleObject()注册之守候操作(即WaitOrTimerCallback委托不再执行)。Unregister(WaitHandle
waitObject)的WaitHandle参数表示成功取消注册之等操作后线程池会向这个目标发出信号(set()),若未思接受此通知可以传递null。

         示例:

图片 16图片 17

private static void Example_RegisterWaitForSingleObject()
{
    // 加endWaitHandle的原因:如果执行过快退出方法会导致一些东西被释放,造成排队的任务不能执行,原因还在研究
    AutoResetEvent endWaitHandle = new AutoResetEvent(false);

    AutoResetEvent notificWaitHandle = new AutoResetEvent(false);
    AutoResetEvent waitHandle = new AutoResetEvent(false);
    RegisteredWaitHandle registeredWaitHandle = ThreadPool.RegisterWaitForSingleObject(
        waitHandle,
        (Object state, bool timedOut) =>
        {
            if (timedOut)
                Console.WriteLine("RegisterWaitForSingleObject因超时而执行");
            else
                Console.WriteLine("RegisterWaitForSingleObject收到WaitHandle信号");
        },
        null, TimeSpan.FromSeconds(2), true
     );

    // 取消等待操作(即不再执行WaitOrTimerCallback委托)
    registeredWaitHandle.Unregister(notificWaitHandle);

    // 通知
    ThreadPool.RegisterWaitForSingleObject(
        notificWaitHandle,
        (Object state, bool timedOut) =>
        {
            if (timedOut)
                Console.WriteLine("第一个RegisterWaitForSingleObject没有调用Unregister()");
            else
                Console.WriteLine("第一个RegisterWaitForSingleObject调用了Unregister()");

            endWaitHandle.Set();
        },
        null, TimeSpan.FromSeconds(4), true
     );

    endWaitHandle.WaitOne();
}

示例

尽上下文

        
上平等略节中说交:线程池最酷线程数设置了很或会见造成Windows频繁执行上下文切换,降低程序性能。对于大部分园友不见面满意这样的回应,我与而同一啊欢喜“知其然,再知其所以然”。

  1. 上下文切换中之“上下文”是什么?

.NET中上下文太多,我最后得出的下结论是:上下文切换中之上下文专指“执行上下文”。

履上下文包括:安全上下文、同步上下文(System.Threading.SynchronizationContext)、逻辑调用上下文(System.Runtime.Messaging.CallContext)。即:安全设置(压缩栈、Thread的Principal属性和Windows身份)、宿主设置(System.Threading.HostExcecutingContextManager)以及逻辑调用上下文数据(System.Runtime.Messaging.CallContext的LogicalSetData()和LogicalGetData()方法)。

  1. 何时实施“上下文切换”?

当一个“时间片”结束时,如果Windows决定重新调度以及一个线程,那么Windows不见面实行上下文切换。如果Windows调度了一个见仁见智的线程,这时Windows执行线程上下文切换。

  1. “上下文切换”造成的性能影响

        
当Windows上下文切换至任何一个线程时,CPU将实施一个不同的线程,而前线程的代码和数目还当CPU的高速缓存中,(高速缓存使CPU不必然经常看RAM,RAM的进度比较CPU高速缓存慢得差不多),当Windows上下文切换至一个新线程时,这个新线程极有或只要执行不一的代码并访问不同之多少,这些代码和数量不在CPU的高速缓存中。因此,CPU必须看RAM来填充它的高速缓存,以回复很快实行状态。但是,在其“时间片”执行完毕后,一次等新的线程上下文切换又出了。

上下文切换所发出的开发不会见转移来任何内存和性质达到的入账。执行上下文所要的岁月在CPU架构和快(即“时间片”的分配)。而填充充CPU缓存所待的时空在系统运作的应用程序、CPU、缓存的深浅及其他各种因素。所以,无法也各一样糟糕线程上下文切换的时日开为来一个确定的价值,甚至无法为出一个估算的值。唯一确定的凡,如果假定构建大性能的应用程序和零部件,就应当尽量避免线程上下文切换。

除了,执行垃圾回收时,CLR必须挂于(暂停)所有线程,遍历它们的栈来查找根以便对堆放着之靶子开展标记,再次遍历它们的仓库(有的对象在减期间产生了活动,所以若创新她的绝望),再恢复所有线程。所以,减少线程的数量为会见肯定升级垃圾回收器的性。每次用一个调试器并撞一个断点,Windows都见面挂于在调试的应用程序中的备线程,并于单步执行要运行应用程序时回升所有线程。因此,你用之线程越多,调试体验也就更加差。

  1. 监Windows上下文切换工具

Windows实际记录了每个线程被上下文切换到之次数。可以行使像Microsoft
Spy++这样的工具查看这数。这个家伙是Visual
Studio附带的一个粗器(vs按安装路径\Visual Studio
2012\Common7\Tools),如图

图片 18

  1. 行上下文类详解

在《异步编程:线程概述及下》遭遇自关系了Thread的一定量单达到下文,即:

1)         CurrentContext       
获取线程正在内部实施之当下达到下文。主要用来线程内部存储数据。

2)         ExecutionContext   
获取一个System.Threading.ExecutionContext对象,该对象涵盖关于当前线程的各种上下文的信息。主要用于线程间数共享。

里头获到的System.Threading.ExecutionContext就是以小节要说之“执行上下文”。

图片 19图片 20

public sealed class ExecutionContext : IDisposable, ISerializable
{
    public void Dispose();
    public void GetObjectData(SerializationInfo info, StreamingContext context);

    // 此方法对于将执行上下文从一个线程传播到另一个线程非常有用。
    public ExecutionContext CreateCopy();
    // 从当前线程捕获执行上下文的一个副本。
    public static ExecutionContext Capture();
    // 在当前线程上的指定执行上下文中运行某个方法。
    public static void Run(ExecutionContext executionContext, ContextCallback callback, object state);

    // 取消执行上下文在异步线程之间的流动。
    public static AsyncFlowControl SuppressFlow();
    public static bool IsFlowSuppressed();
    // RestoreFlow  撤消以前的 SuppressFlow 方法调用的影响。
    // 此方法由 SuppressFlow 方法返回的 AsyncFlowControl 结构的 Undo 方法调用。
    // 应使用 Undo 方法(而不是 RestoreFlow 方法)恢复执行上下文的流动。
    public static void RestoreFlow();
}

View Code

ExecutionContext
类提供的效果为用户代码可以于用户定义之异步点之间捕获和传导此达成下文。公共语言运行时(CLR)确保以托管进程内运行时定义之异步点之间平等地传
ExecutionContext。

当一个线程(初始线程)使用外一个线程(辅助线程)执行任务时,CLR会将前者的行上下文流向(复制到)辅助线程(注意是活动流向是单方向的)。这就算确保了扶持线程执行之别样操作下的凡相同之平安设置与宿主设置。还担保了初始线程的逻辑调用上下文可以以赞助线程中使。

可实施上下文的复制会招一定的性能影响。因为实施上下文中包含大量信,而采访所有这些消息,再把其复制到帮忙线程,要吃多岁月。如果帮线程又采用了再多地帮助线程,还得创造与初始化更多之行上下文数据结构。

于是,为了提升应用程序性能,我们得阻碍实施上下文的流。当然这只有当支援线程不需要或无聘上下文信息之时光才能够展开阻挠。

下面给有一个演示为了演示:

1)         在线程间共享逻辑调用上下文数据(CallContext)。

2)         为了提升性能,阻止\过来执行上下文的流淌。

3)         在时下线程上的指定执行上下文中运行有方法。

图片 21图片 22

private static void Example_ExecutionContext()
{
    CallContext.LogicalSetData("Name", "小红");
    Console.WriteLine("主线程中Name为:{0}", CallContext.LogicalGetData("Name"));

    // 1)   在线程间共享逻辑调用上下文数据(CallContext)。
    Console.WriteLine("1)在线程间共享逻辑调用上下文数据(CallContext)。");
    ThreadPool.QueueUserWorkItem((Object obj) 
        => Console.WriteLine("ThreadPool线程中Name为:\"{0}\"", CallContext.LogicalGetData("Name")));
    Thread.Sleep(500);
    Console.WriteLine();
    // 2)   为了提升性能,取消\恢复执行上下文的流动。
    ThreadPool.UnsafeQueueUserWorkItem((Object obj)
        => Console.WriteLine("ThreadPool线程使用Unsafe异步执行方法来取消执行上下文的流动。Name为:\"{0}\""
        , CallContext.LogicalGetData("Name")), null);
    Console.WriteLine("2)为了提升性能,取消/恢复执行上下文的流动。");
    AsyncFlowControl flowControl = ExecutionContext.SuppressFlow();
    ThreadPool.QueueUserWorkItem((Object obj) 
        => Console.WriteLine("(取消ExecutionContext流动)ThreadPool线程中Name为:\"{0}\"", CallContext.LogicalGetData("Name")));
    Thread.Sleep(500);
    // 恢复不推荐使用ExecutionContext.RestoreFlow()
    flowControl.Undo();
    ThreadPool.QueueUserWorkItem((Object obj) 
        => Console.WriteLine("(恢复ExecutionContext流动)ThreadPool线程中Name为:\"{0}\"", CallContext.LogicalGetData("Name")));
    Thread.Sleep(500);
    Console.WriteLine();
    // 3)   在当前线程上的指定执行上下文中运行某个方法。(通过获取调用上下文数据验证)
    Console.WriteLine("3)在当前线程上的指定执行上下文中运行某个方法。(通过获取调用上下文数据验证)");
    ExecutionContext curExecutionContext = ExecutionContext.Capture();
    ExecutionContext.SuppressFlow();
    ThreadPool.QueueUserWorkItem(
        (Object obj) =>
        {
            ExecutionContext innerExecutionContext = obj as ExecutionContext;
            ExecutionContext.Run(innerExecutionContext, (Object state) 
                => Console.WriteLine("ThreadPool线程中Name为:\"{0}\""<br>                       , CallContext.LogicalGetData("Name")), null);
        }
        , curExecutionContext
     );
}

View Code

结果一旦图:

图片 23

 

 

 注意:

1)        
示例中“在目前线程上的指定执行上下文中运作有方法”:代码中得使用ExecutionContext.Capture()获取当前履行上下文的一个副本

a)        
若直接采用Thread.CurrentThread.ExecutionContext则会报“无法使用以下上下文:
跨 AppDomains 封送的上下文、不是透过捕获操作获取的上下文或都作为 Set
调用的参数的上下文。”错误。

b)        
若使用Thread.CurrentThread.ExecutionContext.CreateCopy()会报“只能复制新近捕获(ExecutionContext.Capture())的上下文”。

2)        
取消执行上下文流动除了使用ExecutionContext.SuppressFlow()方式外。还好透过动ThreadPool的UnsafeQueueUserWorkItem

UnsafeRegisterWaitForSingleObject来实施委托方。原因是不安全的线程池操作不会见传压缩堆栈。每当压缩堆栈流动时,托管的侧重点、同步、区域安装和用户上下文也就流动。

 

线程池线程中的挺

线程池线程中不处理的生将终止进程。以下为是规则的老三栽例外情况: 

  1. 鉴于调用了 Abort,线程池线程中以吸引ThreadAbortException。 
    2.
    由正值卸载应用程序域,线程池线程中以引发AppDomainUnloadedException。 
  2. 公共语言运行库或宿主进程将停线程。

何时休使用线程池线程

而今大家还早已知道线程池为咱提供了福利的异步API及托管的线程管理。那么是休是任何时刻都该使用线程池线程呢?当然不是,我们还是需要“因地制宜”的,在以下几种植状态下,适合给创造并保管自己之线程而非是使用线程池线程:

  1. 用前台线程。(线程池线程“始终”是后台线程)
  2. 待而线程具有一定的事先级。(线程池线程都是默认优先级,“不建议”进行修改)
  3. 任务会长时间占据线程。由于线程池具有极其可怜线程数限制,因此大量占用线程池线程可能会见阻止任务启动。
  4. 消将线程放入单线程单元(STA)。(所有ThreadPool线程“始终”是多线程单元(MTA)中)
  5. 待所有与线程关联的安定团结标识,或如有同线程专用于某一样任务。

 

 

  本博文介绍线程池以及该基础对象池,ThreadPool类的使用和注意事项,如何排队办事起到线程池,执行上下文及线程上下文传递问题…… 

线程池虽然也咱提供了异步操作的利,但是它不支持对线程池中单个线程的错综复杂控制致使我们有若干情况下会一直采用Thread。并且它们对“等待”操作、“取消”操作、“延续”任务等操作比较繁琐,可能驱使你打新造轮子。微软也想到了,所以在.NET4.0之时刻进入了“并行任务”并于.NET4.5遇对那进展改良,想打听“并行任务”的园友可以优先瞧《(译)关于Async与Await的FAQ》。

本节到此结束,感谢大家的鉴赏。赞之言语还伸手多引进啊 (*^_^*)

 

 

 

 

参考资料:《CLR via C#(第三版)》

 

 摘自:http://www.cnblogs.com/heyuquan/archive/2012/12/23/threadPool-manager.html

 

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图