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

图片 2
方法 说明
GetAvailableThreads 剩余空闲线程数
GetMaxThreads 最多可用线程数,所有大于此数目的请求将保持排队状态,直到线程池线程变为可用
GetMinThreads 检索线程池在新请求预测中维护的空闲线程数
QueueUserWorkItem 启动线程池里得一个线程(队列的方式,如线程池暂时没空闲线程,则进入队列排队)
SetMaxThreads 设置线程池中的最大线程数
SetMinThreads 设置线程池最少需要保留的线程数

这里有个类为AutoResetEvent,他的作用是通知正在等待的线程已发生事件。他是从EventWaitHandle继承而来的!例子中分别用了两种不同的方式调用,第二种是可以传递参数的!工作线程1就传递了”我的参数”字符串!工作线程2运行完了会通知等待的线程已发生事件.这里AutoResetEvent初始化的时候首先将信号设成false,工作线程2如果调用成功,将被设成true. asyncOpIsDone.WaitOne()阻塞当前线程,直到收到信号!

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

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

图片 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
2012Common7Tools),如图

图片 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#(第三版)》

 

 摘自:

 

异步编程:使用线程池管理线程 从此图中我们会发现 .NET 与C#
的每个版本发布都是有一个主题…

Program

 

图片 10图片 11

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading;
 6 
 7 namespace ConsoleApplication7
 8 {
 9     class Program
10     {
11         static void Main(string[] args)
12         {
13             Console.WriteLine("n当前线程的HashCode:{0}", Thread.CurrentThread.GetHashCode().ToString());
14             Console.WriteLine("当前应用域的名字为:{0}", AppDomain.CurrentDomain.FriendlyName);
15             int workerThreads;
16             int completionPortThreads;
17 
18             AutoResetEvent asyncOpIsDone = new AutoResetEvent(false);//信号初始为空
19             AutoResetEvent asyncOpIsDone2 = new AutoResetEvent(false);
20             ThreadPool.QueueUserWorkItem(new WaitCallback(MyWork), asyncOpIsDone);
21             ThreadPool.QueueUserWorkItem(new WaitCallback(MyWork1), asyncOpIsDone2);
22             ThreadPool.GetMaxThreads(out workerThreads, out completionPortThreads);
23             Console.WriteLine("得当前由线程池维护的辅助线程的最大数目:{0}", workerThreads);
24             ThreadPool.GetMinThreads(out workerThreads, out completionPortThreads);
25             Console.WriteLine("得当前由线程池维护的空闲辅助线程的最小数目:{0}", workerThreads);
26             /*WaitHandle[] waithandles = new WaitHandle[2];
27             waithandles[0] = asyncOpIsDone;
28             waithandles[1] = asyncOpIsDone2;
29             WaitHandle.WaitAll(waithandles);  */     
30             //或者可以这样
31             asyncOpIsDone.WaitOne();
32             Console.WriteLine("MyWork结束");
33             asyncOpIsDone2.WaitOne();
34             Console.WriteLine("MyWork1结束");
35         }
36         static void MyWork(Object state)
37         {
38             Console.WriteLine("n当前线程的HashCode:{0}", Thread.CurrentThread.GetHashCode().ToString());
39             Console.WriteLine("当前应用域的名字为:{0}", AppDomain.CurrentDomain.FriendlyName);
40             Thread.Sleep(2000);
41             ((AutoResetEvent)state).Set();//表示工作已经完成
42         }
43         static void MyWork1(Object state)
44         {
45             Console.WriteLine("n当前线程的HashCode:{0}", Thread.CurrentThread.GetHashCode().ToString());
46             Console.WriteLine("当前应用域的名字为:{0}", AppDomain.CurrentDomain.FriendlyName);
47             Thread.Sleep(1000);
48             ((AutoResetEvent)state).Set();//表示工作已经完成
49         }
50 
51     }
52 }
using System;
using System.Diagnostics;
using System.Threading;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            const int numberOfOperations = 300;
            var sw = new Stopwatch();
            sw.Start();
            UseThreads(numberOfOperations);
            sw.Stop();
            Console.WriteLine("Execution time using threads: {0}", sw.ElapsedMilliseconds);

            sw.Reset();
            sw.Start();
            UseThreadPool(numberOfOperations);
            sw.Stop();
            Console.WriteLine("Execution time using threadPool: {0}", sw.ElapsedMilliseconds);
        }

        static void UseThreads(int numberOfOperations)
        {
            using (var countdown = new CountdownEvent(numberOfOperations))
            {
                Console.WriteLine("Scheduling work by creating threads");
                for (int i = 0; i < numberOfOperations; i++)
                {
                    var thread = new Thread(() => {
                        Console.Write("{0},", Thread.CurrentThread.ManagedThreadId);
                        Thread.Sleep(TimeSpan.FromSeconds(0.1));
                        countdown.Signal();
                    });
                    thread.Start();
                }
                countdown.Wait();
                Console.WriteLine();
            }
        }

        static void UseThreadPool(int numberOfOperations)
        {
            using (var countdown = new CountdownEvent(numberOfOperations))
            {
                Console.WriteLine("Starting work on a threadpool");
                for (int i = 0; i < numberOfOperations; i++)
                {
                    ThreadPool.QueueUserWorkItem(_ => {
                        Console.Write("{0},", Thread.CurrentThread.ManagedThreadId);
                        Thread.Sleep(TimeSpan.FromSeconds(0.1));
                        countdown.Signal();
                    });
                }
                countdown.Wait();
                Console.WriteLine();
            }
        }
    }
}

 

利用ThreadPool调用工作线程和IO线程的范例

今天我们来谈谈线程池:

图片 12图片 13

图片 14

图片 15图片 16

图片 17

Program

运行结果为

 

好了,今天就到这了!

2、不必管理和维护生存周期短暂的线程,不用在创建时为其分配资源,在其执行完任务之后释放资源。

还有ThreadPool有一个函数RegisterWaitForSingleObject这个函数还是满有意思的!我这里就不再给出例子了!

下面是一个ThreadPool的例子,代码如下:

再看下面的例子

 

 运行的结果为

通过以下两个方法可以读取和设置CLR线程池中工作者线程与I/O线程的最大线程数。

 

ThreadPoolDemo

应用程序可以有多个线程,这些线程在休眠状态中需要耗费大量时间来等待事件发生。其他线程可能进入睡眠状态,并且仅定期被唤醒以轮循更改或更新状态信息,然后再次进入休眠状态。为了简化对这些线程的管理,.NET框架为每个进程提供了一个线程池,一个线程池有若干个等待操作状态,当一个等待操作完成时,线程池中的辅助线程会执行回调函数。线程池中的线程由系统管理,程序员不需要费力于线程管理,可以集中精力处理应用程序任务。通过基础类库中的ThreadPool类提供一个线程池,该线程池可用于发送工作现,处理异步I/O,代表其他线程等待及处理计时器.ThreadPool类的所有方法都是静态方法.ThreadPool本身也是一个静态类.
我们来看看他的定义和一些常用的方法:
 public
static class ThreadPool
{
 [SecuritySafeCritical]
        public static void
GetAvailableThreads(out int workerThreads, out int
completionPortThreads);
        [SecuritySafeCritical]
        public static void
GetMaxThreads(out int workerThreads, out int
completionPortThreads);
        [SecuritySafeCritical]
        public static void
GetMinThreads(out int workerThreads, out int
completionPortThreads);
        [SecuritySafeCritical]
        public static bool
QueueUserWorkItem(WaitCallback callBack);
        [SecuritySafeCritical]
        public static bool
QueueUserWorkItem(WaitCallback callBack, object state);
}
GetAvailableThreads这个方法返回的最大线程池线程数和当前活动线程数之间的差值。
参数 workerThreads: 可用辅助线程的数目。completionPortThreads: 可用异步
I/O 线程的数目。
GetMaxThreads方法获得当前由线程池维护的辅助线程的最大数目.参数
workerThreads: 线程池中辅助线程的最大数目。completionPortThreads:
当前由线程池维护的空闲异步 I/O 线程的最大数目。
GetMinThreads方法获得当前由线程池维护的空闲辅助线程的最小数目.参数
workerThreads:
当前由线程池维护的空闲辅助线程的最小数目.completionPortThreads:
当前由线程池维护的空闲异步 I/O 线程的最小数目。
QueueUserWorkItem方法将方法排入队列以便执行。参数callBack,
表示要执行的方法.state:包含方法所用数据的对象。
下面看个例子:

 

 

图片 18图片 19

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading;
 6 
 7 namespace ConsoleApplication6
 8 {
 9     class Program
10     {
11         static void Main(string[] args)
12         {
13             Console.WriteLine("主线程进行异步条用");
14 
15             AutoResetEvent asyncOpIsDone = new AutoResetEvent(false);
16             ThreadPool.QueueUserWorkItem(new WaitCallback((x) =>
17             {
18 
19                 Thread.Sleep(1000);
20                 Console.WriteLine("工作任务");
21 
22             }));
23             string s="我的参数";
24             ThreadPool.QueueUserWorkItem(new WaitCallback((x) =>
25             {
26                
27                 Thread.Sleep(2000);
28                 Console.WriteLine("工作任务1");
29                 Console.WriteLine(x);
30                
31             }), s);
32             ThreadPool.QueueUserWorkItem(new WaitCallback(MyAsyncOperation), asyncOpIsDone);
33             Console.WriteLine("主线程执行其他");
34             Console.WriteLine("主线程等待任务处理结束");
35             asyncOpIsDone.WaitOne();
36         }
37         static void MyAsyncOperation(Object state)
38         {
39            
40             Thread.Sleep(5000);
41             Console.WriteLine("工作任务2");
42             ((AutoResetEvent)state).Set();
43         }
44     }
45 }
using System;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;

namespace ConsoleApp1
{
    class ThreadPoolDemo
    {
        public ThreadPoolDemo()
        {
        }

        public void Work()
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(CountProcess));
            ThreadPool.QueueUserWorkItem(new WaitCallback(GetEnvironmentVariables));
        }
        /// <summary>  
        /// 统计当前正在运行的系统进程信息  
        /// </summary>  
        /// <param name="state"></param>  
        private void CountProcess(object state)
        {
            Process[] processes = Process.GetProcesses();
            foreach (Process p in processes)
            {
                try
                {
                    Console.WriteLine("进程信息:Id:{0},ProcessName:{1},StartTime:{2}", p.Id, p.ProcessName, p.StartTime);
                }
                catch (Win32Exception e)
                {
                    Console.WriteLine("ProcessName:{0}", p.ProcessName);
                }
                finally
                {
                }
            }
            Console.WriteLine("获取进程信息完毕。");
        }
        /// <summary>  
        /// 获取当前机器系统变量设置  
        /// </summary>  
        /// <param name="state"></param>  
        public void GetEnvironmentVariables(object state)
        {
            IDictionary list = System.Environment.GetEnvironmentVariables();
            foreach (DictionaryEntry item in list)
            {
                Console.WriteLine("系统变量信息:key={0},value={1}", item.Key, item.Value);
            }
            Console.WriteLine("获取系统变量信息完毕。");
        }
    }
}

本例中可以详细看AutoRestEvent的用法MyWork1实际上先结束的,但是asyncOpIsDone需要等MyWork的信号!所以先输出了MyWork结束.这里还有一个东西我想说的就是WaitHandle,上面的例子已经给出了他的用法!他是AutoRsetEvent的基类.

public static bool QueueUserWorkItem(WaitCallback
callBack):将方法排入队列以便执行。此方法在有线程池线程变得可用时执行。

 

public static bool QueueUserWorkItem(WaitCallback
callBack,Object
state):将方法排入队列以便执行,并指定包含该方法所用数据的对象。此方法在有线程池线程变得可用时执行。

如果需要传递任务信息可以利用WaitCallback中的state参数,类似于ParameterizedThreadStart委托。

总之使用线程池的作用就是减少创建和销毁线程的系统开销。在.NET中有一个线程的类ThreadPool,它提供了线程池的管理。

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图