异步编制程序

 

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

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

图片 1

 从此图中大家会开采 .NET 与C#
的每种版本发布都以有二个“主旨”。即:C#1.0托管代码→C#2.0泛型→C#3.0LINQ→C#4.0动态语言→C#5.0异步编制程序。以往小编为流行版本的“异步编制程序”核心写连串分享,期待你的查看及点评。

 

现行反革命的应用程序更加的复杂,我们平时须要选用《异步编制程序:线程概述及应用》中关系的八线程技术来升高应用程序的响应速度。那时大家反复的开创和销毁线程来让应用程序快速响应操作,那频仍的成立和销毁无疑会下降应用程序质量,大家得以引进缓存机制消除那几个难点,此缓存机制亟待解决如:缓存的大大小小意思、排队实行任务、调治空闲线程、按需创立新线程及销毁多余空闲线程……方今微软现已为大家提供了现存的缓存机制:线程池

        
线程池原自于对象池,在事无巨细表达明线程池前让我们先来打听下何为对象池。

流程图:

 图片 2

 

         对于对象池的清理平时设计两种方法:

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

2)         自动清理,即透过System.Threading.提姆er来完毕定期清理。

 

最重要达成代码:

 

  图片 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 maxIdle提姆e = 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 马努alReleaseObject(){ } ///
<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.Get马克斯Threads(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的信托,并点名一个 叁十六人有旗号整数来代表超时值(以纳秒为单位)。 // 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)         使用Get马克斯Threads()和Set马克斯Threads()获取和安装最大线程数

可排队到线程池的操作数仅受内部存款和储蓄器的范围;而线程池限制进度中能够而且处于活动状态的线程数(暗中同意情状下,限制每种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)         三个参数

措施Get马克斯Threads()、Set马克斯Threads()、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则表示内核查象每趟接到频域信号,线程池线程都会实践回调方法。等待三个AutoReset伊芙nt对象时,那一个职能更是有用。

3)         RegisterWaitForSingleObject()方法重临贰个RegisteredWaitHandle对象的引用。那么些指标标记了线程池正在它下面等待的基业对象。大家得以调用它的Unregister(WaitHandle
waitObject)方法撤废由RegisterWaitForSingleObject()注册的守候操作(即WaitOrTimerCallback委托不再试行)。Unregister(WaitHandle
waitObject)的WaitHandle参数表示成功撤除注册的等候操作后线程池会向此目的发出确定性信号(set()),若不想摄取此公告能够传递null。

         示例:

图片 5private
static void Example_RegisterWaitForSingleObject() { //
加endWaitHandle的缘故:假设进行过快退出形式会招致有个别事物被放走,变成排队的天职不能够实行,原因还在切磋AutoReset伊芙nt endWaitHandle = new AutoResetEvent(false); AutoReset伊夫nt
notificWaitHandle = new AutoReset伊夫nt(false); AutoReset伊夫nt waitHandle
= new AutoReset伊夫nt(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、缓存的分寸以及其余各样因素。所以,不恐怕为每二遍线程上下文切换的小时支付给出三个规定的值,以致惊慌失措提交一个预计的值。唯一确定的是,假使要创设高品质的应用程序和组件,就应有尽只怕防止线程上下文切换。

除去,实践垃圾回收时,CLGL450必须挂起(暂停)全体线程,遍历它们的栈来查找根以便对堆中的对象开始展览标志,再度遍历它们的栈(有的对象在回落时期发生了活动,所以要更新它们的根),再复苏全体线程。所以,裁减线程的数据也会肯定晋级垃圾回收器的品质。每回使用二个调试器并蒙受贰个断点,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(塞里alizationInfo 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
类提供的效果让用户代码能够在用户定义的异步点之间捕获和传导此上下文。公共语言运营时(CL凯雷德)确认保障在托管进程内运营时定义的异步点之间一样地传输
ExecutionContext。

每当一个线程(开端线程)使用另叁个线程(协助线程)实行任务时,CL昂Cora会将前者的实施上下文流向(复制到)帮助线程(注意那些活动流向是单方向的)。那就保证了帮手线程施行的其余操作使用的是平等的平安设置和宿主设置。还打包票了早先线程的逻辑调用上下文能够在救助线程中应用。

但施行上下文的复制会产生一定的习性影响。因为施行上下文中包括大量音信,而采访全部那么些消息,再把它们复制到帮忙线程,要成本成千上万时间。纵然帮忙线程又选取了越来越多地支持线程,还非得创立和开始化更加多的实施上下文数据结构。

所以,为了提高应用程序质量,咱们得以阻碍推行上下文的流动。当然那只有在赞助线程没有要求还是不访问上下文消息的时候能力进行阻拦。

下边给出三个演示为了演示:

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》。

本节到此结束,谢谢大家的鉴赏。赞的话还请多引入啊 (*^_^*)

 

 

 

 

参考资料:《CLCRUISER 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#
的每一种版本公布都以有二个宗旨…

到现在我们都曾经知道线程池为大家提供了便于的异步API及托管的线程处理。那么是还是不是任曾几何时候都应有使用线程池线程呢?当然不是,大家依旧要求“因人而异”的,在偏下两种情况下,适合于创建并管制本身的线程而不是使用线程池线程:

         示例:

本节到此甘休,多谢我们的鉴赏。赞的话还请多引入啊 (*^_^*)

上边给出二个演示为了演示:

上下文切换所产生的费用不会换成任何内部存款和储蓄器和属性上的收入。实践上下文所需的时辰取决于CPU架商谈速度(即“时间片”的分配)。而填充CPU缓存所需的时间取决于系统运作的应用程序、CPU、缓存的分寸以及别的各个因素。所以,无法为每三回线程上下文切换的日子支付给出二个规定的值,乃至不知所可提交一个估算的值。唯一明确的是,即便要营造高品质的应用程序和组件,就应该尽大概制止线程上下文切换。

  1. 线程池线程数

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

2)         名字为executeOnlyOnce的Boolean参数。传true则象征线程池线程只进行回调方法二回;若传false则代表内核查象每一趟接到连续信号,线程池线程都会执行回调方法。等待二个AutoReset伊芙nt对象时,那一个作用特别有用。

图片 10

示例

c)        
贰个行使程序池是三个独自的进度,具备贰个线程池,应用程序池中能够有两个WebApplication,每一种运转在一个独立的AppDomain中,这几个WebApplication公用一个线程池。

图片 11

在开发银行线程池时,线程池具备四个放置延迟,用于启用最小空闲线程数,以增加应用程序的吞吐量。

通过静态方法GetAvailableThreads()重回的线程池线程的最大数目和当前移动数量之间的差值,即获取线程池中当前可用的线程数目

 

为制止向线程分配不要求的库房空间,线程池遵照一定的时日间隔创设新的悠闲线程(该距离为半秒)。所以一旦最小空闲线程数设置的过小,在短时间内实行大气任务会因为创制新空闲线程的放手延迟导致品质瓶颈。最小空闲线程数暗中认可值等于机械上的CPU核数,并且不建议更动最小空闲线程数。

 

 图片 12

  1. 排队办事项

 

        
上一小节中提起:线程池最大线程数设置过大恐怕会促成Windows频仍试行上下文切换,降低程序质量。对于绝大好些个园友不会满意那样的回答,笔者和你同样也喜爱“知其然,再知其所以然”。

 

但执行上下文的复制会产生一定的个性影响。因为实施上下文中包涵大批量音讯,而采访全部那一个讯息,再把它们复制到扶助线程,要开销数不尽年华。假若帮忙线程又选择了越来越多地推来推去线程,还必须创立和初阶化更多的进行上下文数据结构。

 

        
当Windows上下文切换成另三个线程时,CPU将实践三个不如的线程,而在此之前线程的代码和数据还在CPU的高速缓存中,(高速缓存使CPU不必平日访问RAM,RAM的速度比CPU高速缓存慢得多),当Windows上下文切换来二个新线程时,这一个新线程极有不小希望要试行不一的代码并走访差异的数目,这一个代码和多少不在CPU的高速缓存中。由此,CPU必须访问RAM来填充它的高速缓存,以平复相当慢实市价况。可是,在其“时间片”试行完后,三回新的线程上下文切换又爆发了。

兑现的主要代码

View Code

可排队到线程池的操作数仅受内部存款和储蓄器的限定;而线程池限制进度中得以同不常候处于活动状态的线程数(暗中认可意况下,限制每个CPU 能够应用 25 个工作者线程和 1,000 个 I/O 线程(根据机器CPU个数和.net
framework版本的两样,那个多少恐怕会有转换)),全数大于此数据的呼吁将保证排队情状,直到线程池线程变为可用。

 

 从此图中大家会开掘 .NET 与C#
的每种版本公布都以有三个“宗旨”。即:C#1.0托管代码→C#2.0泛型→C#3.0LINQ→C#4.0动态语言→C#5.0异步编制程序。今后本身为流行版本的“异步编制程序”核心写连串分享,期待你的查看及点评。

  1. 奉行上下文类详解

 

图片 13图片 14

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
     );
}

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

3)         静态方法GetAvailableThreads()

几时不使用线程池线程

措施Get马克斯Threads()、Set马克斯Threads()、GetMinThreads()、SetMinThreads()、GetAvailableThreads()钧包罗多个参数。参数workerThreads指工小编线程;参数completionPortThreads指异步
I/O 线程。

线程池线程中未管理的老新秀终止进度。以下为此规则的三种例外景况: 

结果如图:

 注意:

  1. 出于调用了 Abort,线程池线程中将掀起ThreadAbortException。 
    2.
    是因为正值卸载应用程序域,线程池线程中将引发AppDomainUnloadedException。 
  2. 集体语言运转库或宿主进度将截止线程。

让一个线程不分明地守候七个基本对象进入可用状态,那对线程的内部存款和储蓄器财富来讲是一种浪费。ThreadPool.RegisterWaitForSingleObject()为大家提供了一种格局:在三个水源对象变得可用的时候调用四个格局。

线程池ThreadPool类详解

 

图片 15图片 16

当三个“时间片”结束时,假设Windows决定再度调节同一个线程,那么Windows不会实行上下文切换。固然Windows调整了二个两样的线程,那时Windows实施线程上下文切换。

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

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);
    ……
}

图片 17

 

3)         在脚下线程上的内定施行上下文中运作有些方法。

除此之外,施行垃圾回收时,CLENCORE必须挂起(暂停)全体线程,遍历它们的栈来查找根以便对堆中的对象举办标志,再一次遍历它们的栈(有的对象在调减时期发生了活动,所以要立异它们的根),再复苏全体线程。所以,裁减线程的数目也会明确升高垃圾回收器的品质。每一趟使用一个调节和测试器并蒙受二个断点,Windows都会挂起正在调节和测试的应用程序中的全数线程,并在单步实践或运转应用程序时上涨全体线程。因而,你用的线程更加多,调试体验也就越差。

ThreadPool静态类,为应用程序提供贰个由系统管理的帮衬线程池,从而使您能够集中精力于应用程序职责而不是线程管理。每个进程都有一个线程池,二个Process中不得不有贰个实例,它在相继应用程序域(AppDomain)是共享的。

4)         五个参数

流程图:

1)         使用Get马克斯Threads()和Set马克斯Threads()获取和安装最大线程数

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){ }
}

ExecutionContext
类提供的效率让用户代码能够在用户定义的异步点之间捕获和传导此上下文。公共语言运转时(CL汉兰达)确认保障在托管进度内运营时定义的异步点之间同样地传输
ExecutionContext。

参谋资料:《CLRAV4 via C#(第三版)》

在中间,线程池将协调的线程划分工小编线程(匡助线程)和I/O线程。前者用于实行平日的操作,后者专项使用于异步IO,举例文件和网络请求,注意,分类并不表达二种线程自己有距离,内部仍然是同样的。

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();
}

2)         为了进步品质,阻止\过来试行上下文的流淌。

实行上下文

View Code

在线程池运维中,对于实施完职分的线程池线程,不会应声销毁,而是重返到线程池,线程池会维护最小的空闲线程数(即便应用程序全部线程都是悠闲状态),以便队列任务能够立刻运维。超越此最小数目标悠闲线程一段时间没事做后会自个儿醒来终止自身,以节省系统财富。

一旦你精通调用方的货仓与在排队职责实施时期施行的具备平安全检查查不相干,则还能运用不安全的不二秘籍ThreadPool.UnsafeQueueUserWorkItem 和
ThreadPool.UnsafeRegisterWaitForSingleObject。QueueUserWorkItem 和
RegisterWaitForSingleObject
都会捕获调用方的饭店,此仓库将要线程池线程起先举办职务时合并到线程池线程的库房中。假使急需开始展览安检,则必须检查整个旅社,但它还会有所一定的性质开支。使用“不安全的”方法调用并不会提供相对的黑河,但它会提供更加好的本性。

 

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

图片 18图片 19

个中获得到的System.Threading.ExecutionContext便是本小节要说的“实践上下文”。

2)         自动清理,即通过System.Threading.Timer来达成定时清理。

Windows实际记录了种种线程被上下文切换到的次数。能够行使像Microsoft
Spy++那样的工具查看这些数额。这些工具是Visual
Studio附带的三个小工具(vs按安装路线\Visual Studio
2012\Common7\Tools),如图

b)        
其实FileStream的异步读写,异步发送接受Web请求,System.Threading.Timer机械漏刻,以至利用delegate的beginInvoke都会暗中认可调用
ThreadPool,也正是说不唯有你的代码只怕利用到线程池,框架之中也或许采纳到。

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

 

图片 20图片 21

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

于今的应用程序更加的复杂,大家平常须求利用《异步编制程序:线程概述及采用》中提到的四线程技术来加强应用程序的响应速度。那时大家反复的创办和销毁线程来让应用程序迅速响应操作,那频繁的成立和销毁无疑会减低应用程序品质,大家得以引进缓存机制消除那个难题,此缓存机制亟待化解如:缓存的大不荒谬、排队实行任务、调节空闲线程、按需成立新线程及销毁多余空闲线程……近来微软早已为我们提供了现有的缓存机制:线程池

 

         对于对象池的清理平时设计二种办法:

1)         WaitOrTimerCallback委托参数,该信托接受三个名叫timeOut的Boolean参数。若是 WaitHandle 在指定时期内并未有接受时域信号(即,超时),则为true,否则为 false。回调方法能够根据timeOut的值来针对地选用措施。

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();
}

线程池即便为我们提供了异步操作的便利,然而它不扶助对线程池中单个线程的头昏眼花调节致使大家有些意况下会间接动用Thread。并且它对“等待”操作、“打消”操作、“一连”任务等操作比较繁琐,也许驱令你从新造轮子。微软也想开了,所以在.NET4.0的时候进入了“并行职责”并在.NET4.5中对其展开革新,想打听“并行职务”的园友能够先看看《(译)关于Async与Await的FAQ》

每当多个线程(初步线程)使用另贰个线程(帮助线程)试行职务时,CLCR-V会将前者的试行上下文流向(复制到)扶助线程(注意那几个活动流向是单方向的)。那就确认保障了声援线程推行的其他操作使用的是同样的安全设置和宿主设置。还担保了伊始线程的逻辑调用上下文能够在帮扶线程中应用。

不提出退换线程池中的最大线程数:

ThreadPool

 

1)         CurrentContext       
获取线程正在内部试行的近来上下文。重要用来线程内部存款和储蓄数据。

 

  1. 监视Windows上下文切换工具

        
线程池原自于对象池,在详细表达明线程池前让大家先来打听下何为对象池。

 

利用需注意:

因此调用 ThreadPool.QueueUserWorkItem 并传递 WaitCallback
委托来使用线程池。也足以经过应用 ThreadPool.RegisterWaitForSingleObject
并传递 WaitHandle(在向其发出功率信号或过期时,它将掀起对由
WaitOrTimerCallback
委托包装的方法的调用)来将与等待操作相关的行事项排队到线程池中。若要撤消等待操作(即不再推行WaitOr提姆erCallback委托),可调用RegisterWaitForSingleObject()方法重返的RegisteredWaitHandle的
Unregister 方法。

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

a)        
若直接使用Thread.CurrentThread.ExecutionContext则会报“不大概采纳以下上下文:
跨 AppDomains 封送的上下文、不是通过捕获操作获取的上下文或已当做 Set
调用的参数的上下文。”错误。

通过对“对象池”的贰个概略认识能帮我们越来越快通晓线程池。

 

图片 22图片 23

线程池线程中的非凡

因而,为了进步应用程序品质,大家得以阻碍实践上下文的流动。当然那只有在帮忙线程无需照旧不访问上下文音讯的时候手艺张开拦阻。

1)        
示例中“在当前线程上的钦定实践上下文中运作有些方法”:代码中必须使用ExecutionContext.Capture()获取当前实行上下文的二个别本

2)         ExecutionContext   
获取贰个System.Threading.ExecutionContext对象,该对象涵盖关于当前线程的各类上下文的音讯。首要用于线程间数据共享。

2)        
撤消实行上下文流动除了使用ExecutionContext.SuppressFlow()情势外。还是能够透过利用ThreadPool的UnsafeQueueUserWorkItem

UnsafeRegisterWaitForSingleObject来实行委托方法。原因是不安全的线程池操作不会传导压缩酒馆。每当压缩商旅流动时,托管的宗旨、同步、区域安装和用户上下文也随后流动。

  1. 内需前台线程。(线程池线程“始终”是后台线程)
  2. 亟待使线程具备特定的优先级。(线程池线程都以暗许优先级,“不建议”举办改换)
  3. 职务会长期攻陷线程。由于线程池具备最大线程数限制,因而多量占用线程池线程大概会阻碍任务运营。
  4. 内需将线程放入单线程单元(STA)。(全体ThreadPool线程“始终”是四线程单元(MTA)中)
  5. 须求持有与线程关联的手舞足蹈标记,或使某一线程专项使用于某一职分。

《异步编程:线程概述及选拔》中本人关系了Thread的多个上下文,即:

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

 

  1. “上下文切换”变成的属性影响
  1. 在一个基本构造可用时调用一个办法

a)        
将线程池大小设置得太大,或然会招致更频仍的进行上下文切换及深化财富的争用情况。

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

  1. 上下文切换中的“上下文”是怎么着?

 

一言九鼎达成代码:

3)         RegisterWaitForSingleObject()方法重回多个RegisteredWaitHandle对象的引用。那么些指标标记了线程池正在它上边等待的内查对象。大家能够调用它的Unregister(WaitHandle
waitObject)方法裁撤由RegisterWaitForSingleObject()注册的守候操作(即WaitOrTimerCallback委托不再试行)。Unregister(WaitHandle
waitObject)的WaitHandle参数表示成功取消注册的等候操作后线程池会向此目的发出实信号(set()),若不想接收此文告可以传递null。

  本博文介绍线程池以及其基础对象池,ThreadPool类的利用及注意事项,怎么着排队办事项到线程池,实践上下文及线程上下文字传递递难点…… 

相关文章