线程池是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。每个进程只有一个线程池对象。
下面说一下线程池中的异常,在线程池中未处理的异常将终止进程。以下为此规则的三种例外情况:
(1)由于调用了Abort,线程池线程中将引发ThreadAbortException异常(在对Abort方法进行调用时引发的异常)。
(2)由于正在卸载应用程序域,线程池线程中将引发AppDomainUnloadedException异常(在尝试访问已卸载的应用程序域时引发的异常)。
(3)公共语言运行库或宿主进程将终止线程。
如果公共语言运行库所创建的线程中未处理这些异常中的任何一个,则异常将终止线程,但公共语言运行库不允许该异常继续下去。
如果在主线程或从非托管代码进入运行库的线程中未处理这些异常,则它们将正常继续,并导致应用程序终止。
注意:在 .NET Framework 1.0 和 1.1 版中,公共语言运行库将捕获线程池中的未处理异常,而不出现任何提示。这可能会破坏应用程序状态,并最终导致应用程序挂起,将很难进行调试。
使用线程池的方式主要有4种,下面分别对其进行介绍。
1.ThreadPool类的QueueUserWorkItem方法
在使用线程池时,可以从托管代码中调用ThreadPool类的QueueUserWorkItem方法,或从非托管代码中调用CorQueueUserWorkItem方法,并用线程池线程要执行的回调方法WaitCallback执行线程池。
QueueUserWorkItem方法将方法排入队列以便执行(并指定包含该方法所用数据的对象,用state参数来实现)。此方法在有线程池线程变得可用时执行。该方法有两个语法形式,其语法如下:
public static bool QueueUserWorkItem(WaitCallback callBack) public static bool QueueUserWorkItem(WaitCallback callBack,Object state)
参数说明:
callBack:WaitCallback类型,它表示要执行的方法。
State:Object类型,包含方法所用数据的对象。
返回值:Boolean类型,如果此方法成功排队,则为true;如果无法将该工作项排队,则引发OutOfMemoryException异常。
示例 线程池的应用
本示例用线程池顺序执行两个方法。代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace ThreadPoolApple { class Program { public void thread1(Object obj)//定义方法thread1 { for (int i = 0; i <= 3; i++)//输出0~3的值 { Console.Write(i.ToString()); } Console.WriteLine();//换行 } public void thread2(Object obj)//定义方法thread2 { for (int i = 4; i <= 6; i++)//输出4~6的值 { Console.Write(i.ToString() + obj.ToString());//值后面加货币符号 } Console.WriteLine();//换行 } static void Main(string[] args) { string Ostr = "¥";//用字符串记录货币符号 Program prog = new Program();//实例化Program类 for (int i = 0; i <= 3; i++) { //用线程池执行无参数方法 ThreadPool.QueueUserWorkItem(new WaitCallback(prog.thread1)); //用线程池执行有参数方法 ThreadPool.QueueUserWorkItem(new WaitCallback(prog.thread2),Ostr); } Console.ReadLine(); } } }
运行结果如下图所示。
图 线程池的应用结果
注意:在ThreadPool类中QueueUserWorkItem是一个静态方法,因此可以由ThreadPool类直接用。
2.ThreadPool类的UnsafeQueueUserWorkItem方法
该方法注册一个等待WaitHandle的委托。与QueueUserWorkItem方法不同,UnsafeQueueUserWorkItem 不会将调用堆栈传播到辅助线程。这使得代码可以失去调用堆栈,从而提升它的安全特权。语法如下:
[SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.NoFlags|SecurityPermissionFlag.ControlEvidence|SecurityPermissionFlag.ControlPolicy)] public static bool UnsafeQueueUserWorkItem(WaitCallback callBack,Object state)
参数说明:
callBack :System.Threading.WaitCallback类型,一个WaitCallback,表示当线程池中的线程选择工作项时调用的委托。
state:Object类型,在接受线程池服务时传递给委托的对象。
返回值:Boolean类型,如果方法成功,则为true;如果无法将该工作项排队,则引发OutOfMemoryException。
在使用 UnsafeQueueUserWorkItem方法时,可能会无意中打开一个安全漏洞。代码访问安全性的权限检查基于所有调用方对堆栈的权限进行。如果使用 UnsafeQueueUserWorkItem 将工作排在某个线程池线程上,则该线程池线程的堆栈将不会具有实际调用方的上下文。恶意代码可能会利用这一点避开权限检查。
示例 线程池的应用
本示例主要讲解一下如何用UnsafeQueueUserWorkItem方法对线程池进行操作。代码如下:
namespace _01_05 { class Program { static void Main(string[] args) { //注册一个等待WaitHandle的委托 ThreadPool.UnsafeQueueUserWorkItem(new WaitCallback(ThreadProc), "First task"); Console.WriteLine("将主线程挂起"); Thread.Sleep(1000);//挂走主线程 Console.WriteLine("执行主线程"); Console.ReadLine(); } //自定义方法,主要用于线程池的调用方法 public static void ThreadProc(object state) { Console.WriteLine("执行线程池 "+state.ToString()); } } }
运行结果如下图所示。
与QueueUserWorkItem方法不同,UnsafeQueueUserWorkItem不会将调用堆栈传播到辅助线程。这使得代码可以失去调用堆栈,从而提升它的安全特权。
3.ThreadPool类的RegisterWaitForSingleObject方法
可以使用 ThreadPool类的RegisterWaitForSingleObject方法注册正在等待WaitHandle的委托。该方法一共有4个语法形式,下面分别对其进行介绍。
l 语法1
注册一个等待WaitHandle的委托,并指定一个32位有符号整数来表示超时值(以毫秒为单位)。其语法如下:
public static RegisteredWaitHandle RegisterWaitForSingleObject(WaitHandle waitObject,WaitOrTimerCallback callBack,Object state,int millisecondsTimeOutInterval,bool executeOnlyOnce)
waitObject:WaitHandle类型,要注册的WaitHandle。使用WaitHandle而非Mutex。
CallBack:WaitOrTimerCallback类型,waitObject参数终止时调用的WaitOrTimerCallback委托。
State:Object类型,传递给委托的对象。
MillisecondsTimeOutInterval:Int32类型,以毫秒为单位的超时。如果millisecondsTimeOutInterval参数为0(零),函数将测试对象的状态并立即返回。如果millisecondsTimeOutInterval为-1,则函数的超时间隔永远不过期。
ExecuteOnlyOnce:Boolean类型,如果为true,表示在调用了委托后,线程将不再在waitObject 参数上等待;如果为false,表示每次完成等待操作后都重置计时器,直到注销等待。
返回值:System.Threading.RegisteredWaitHandle类型,封装本机句柄的 RegisteredWaitHandle。
l 语法2
注册一个等待WaitHandle的委托,并指定一个64位有符号整数来表示超时值(以毫秒为单位)。其语法如下:
public static RegisteredWaitHandle RegisterWaitForSingleObject(WaitHandle waitObject,WaitOrTimerCallback callBack,Object state,long millisecondsTimeOutInterval,bool executeOnlyOnce)
参数说明:
waitObject:WaitHandle类型,要注册的WaitHandle。使用WaitHandle而非Mutex。
CallBack:WaitOrTimerCallback类型,waitObject参数终止时调用的WaitOrTimerCallback委托。
State:Object类型,传递给委托的对象。
millisecondsTimeOutInterval:Int64类型,以毫秒为单位的超时。如果millisecondsTimeOutInterval参数为0(零),函数将测试对象的状态并立即返回。如果millisecondsTimeOutInterval为-1,则函数的超时间隔永远不过期。
ExecuteOnlyOnce:Boolean类型,如果为true,表示在调用了委托后,线程将不再在waitObject 参数上等待;如果为false,表示每次完成等待操作后都重置计时器,直到注销等待。
返回值:System.Threading.RegisteredWaitHandle类型,封装本机句柄的 RegisteredWaitHandle。
l 语法3
注册一个等待WaitHandle的委托,并指定一个TimeSpan值来表示超时时间。其语法如下:
public static RegisteredWaitHandle RegisterWaitForSingleObject(WaitHandle waitObject,WaitOrTimerCallback callBack,Object state,TimeSpan timeout,bool xecuteOnlyOnce)
参数说明:
waitObject:WaitHandle类型,要注册的WaitHandle。使用WaitHandle而非Mutex。
CallBack:WaitOrTimerCallback类型,waitObject参数终止时调用的WaitOrTimerCallback委托。
State:Object类型,传递给委托的对象。
Timeout:TimeSpan类型,TimeSpan表示的超时时间。如果timeout为0(零),则函数将测试对象的状态并立即返回。如果timeout为-1,则函数的超时间隔永远不过期。
ExecuteOnlyOnce:Boolean类型,如果为true,表示在调用了委托后,线程将不再在waitObject 参数上等待;如果为false,表示每次完成等待操作后都重置计时器,直到注销等待。
返回值:System.Threading.RegisteredWaitHandle类型,封装本机句柄的 RegisteredWaitHandle。
l 语法4
指定表示超时(以毫秒为单位)的32位无符号整数,注册一个委托等待WaitHandle。其语法如下:
[CLSCompliantAttribute(false)] public static RegisteredWaitHandle RegisterWaitForSingleObject(WaitHandle waitObject,WaitOrTimerCallback callBack,Object state,uint millisecondsTimeOutInterval,bool executeOnlyOnce)
参数说明:
waitObject:WaitHandle类型,要注册的WaitHandle。使用WaitHandle而非Mutex。
CallBack:WaitOrTimerCallback类型,waitObject参数终止时调用的WaitOrTimerCallback委托。
State:Object类型,传递给委托的对象。
MillisecondsTimeOutInterval:UInt32类型,以毫秒为单位的超时。如果millisecondsTimeOutInterval参数为0(零),函数将测试对象的状态并立即返回。如果millisecondsTimeOutInterval为-1,则函数的超时间隔永远不过期。
ExecuteOnlyOnce:Boolean类型,如果为true,表示在调用了委托后,线程将不再在waitObject参数上等待;如果为false,表示每次完成等待操作后都重置计时器,直到注销等待。
返回值:System.Threading.RegisteredWaitHandle类型,封装本机句柄的 RegisteredWaitHandle。
注意:在以上的4个语法中,对waitObject应用Mutex不会导致回调互斥,因为它是基于Win32 API使用默认的WT_EXECUTEDEFAULT标志,所以每次回调都在单独的线程池线程上调度。因此,请尽可能的不要使用Mutex,应该使用最大计数为1的Semaphore。
RegisterWaitForSingleObject方法将指定的委托排队到线程池。当发生以下两种情况时,辅助线程将执行委托:
l 指定对象处于终止状态。
l 超时间隔已过期。
RegisterWaitForSingleObject方法检查指定对象的WaitHandle的当前状态。如果对象状态为非终止状态,则此方法将注册一个等待操作,该等待操作由线程池中的一个线程来执行。当对象状态变为终止或超时间隔已过期时,委托由辅助线程执行。如果 timeOutInterval参数不为0(零),并且executeOnlyOnce参数为false,则每当事件收到信号或超时间隔过期时都会重置计时器。若要取消等待操作,请调用RegisteredWaitHandle.Unregister方法。
等待线程使用Win32的WaitForMultipleObjects函数来监视已注册的等待操作。因此,如果必须在对RegisterWaitForSingleObject的多次调用中使用相同的本机操作系统句柄,则必须使用Win32的DuplicateHandle函数重复该句柄。这里要注意的是,不应为传递到RegisterWaitForSingleObject的事件对象发出脉冲,这是因为等待线程在重置前可能不会检测到该事件已终止。
返回前,函数将修改某些类型的同步对象的状态。修改仅发生在其终止状态满足等待条件的对象上。例如,信号量计数减少一。
下面通过一个简单的例子,来说明一下如何使用RegisterWaitForSingleObject方法对线程池进行相应的操作。代码如下:
示例 线程池的应用
本示例通过注册正在等待的WaitHandle委托,对线程池进行操作。代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace _01_04 { class Program { public class TaskInfo//定义一个类 { public RegisteredWaitHandle Handle = null;//定义一个句柄变量 public string OtherInfo = "default";//定义一个字符串 } static void Main(string[] args) { AutoResetEvent ev = new AutoResetEvent(false);//实例化等待线程已发生的事件,将初始状态设置为非终止 TaskInfo ti = new TaskInfo();//实例化类 ti.OtherInfo = "First task";//记录文本信息 ti.Handle = ThreadPool.RegisterWaitForSingleObject( ev,//要进行注册的WaitHandle类型 new WaitOrTimerCallback(WaitProc),//当WaitHandle超时或终止时要调用的方法 ti,//传递给委托对象的值 1000,//设置超时间隔 false//表示每次完成等待操作后都重置计时器,直到注销等待 );//注册一个等待的委托 Thread.Sleep(3100);//将主线程挂起 Console.WriteLine("执行主线程"); ev.Set();//将WaitHandle设置为终止 Console.WriteLine("将等待线程已发生的事件设置为终止"); Console.ReadLine(); } //state:一个对象,包含回调方法在每次执行时要使用的信息 //timedOut:如果 WaitHandle 超时,则为 true;如果其终止,则为 false。 public static void WaitProc(object state, bool timedOut) { TaskInfo ti = (TaskInfo)state; if (!timedOut)//当WaitHandle为终止时 { if (ti.Handle != null)//如果委托对象的句柄不为空 ti.Handle.Unregister(null);//取消RegisterWaitForSingleObject方法所发出的已注册等待操作 } string StrTime = timedOut ? " WaitHandle 超时" : "终止"; Console.WriteLine("当前执行所使用的信息:" + state.ToString() + "; " + StrTime + "; 传递给委托的对象:" + ti.OtherInfo); } } }
运行结果如下图所示。
图 RegisterWaitForSingleObject方法的应用
在上图可以看出,在上例并没有使用for等循环语句执行线程池(ThreadPool)中的RegisterWaitForSingleObject方法,但它却执行了3次,这是为什么呢?其主要原因是将RegisterWaitForSingleObject方法中的ExecuteOnlyOnce参数设置为false(如果将其设置为true,调用了委托后,线程将不在waitObject参数上等待,也就是只执行一次),它表示每次完成等待操作后都重置计时器,直到取消由RegisterWaitForSingleObject方法发出的已经注册等待的操作,主要是用State参数来实现的,为了可以在该参数中记录所传递的委托对象,事先必须要定义一个类,在类中定义两个全局变量,用于记录委托的信息,以及本机的句柄。其类的定义如下:
public class TaskInfo//定义一个类 { public RegisteredWaitHandle Handle = null;//定义一个句柄变量 public string OtherInfo = "default";//定义一个字符串 }
然后通过自定义类的Handle变量记录本机句柄,用OtherInfo变量记录委托信息,如果想要取消RegisterWaitForSingleObject方法所发出的已注册的等待操作,可以用Handle变量的Unregister(null)方法取消注册。
为了读者能更好的定义RegisterWaitForSingleObject方法所执行的自定义事件,下面对RegisterWaitForSingleObject方法中CallBack参数所调用的方法进行说明,CallBack参数是WaitHandle类型的,主要是当WaitHandle超时或终止时所调用的方法,其语法结构为:
[ComVisibleAttribute(true)] public delegate void WaitOrTimerCallback(Object state,bool timedOut)
参数说明:
state:Object类型,一个对象,包含回调方法在每次执行时要使用的信息。
TimedOut:Boolean类型,如果WaitHandle超时,则为true;如果其终止,则为false。
通过以上语法,用户可自定义一个方法,用于线程池的调用,代码如下:
public static void WaitProc(object state, bool timedOut) { }