线程池是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。每个进程只有一个线程池对象。

下面说一下线程池中的异常,在线程池中未处理的异常将终止进程。以下为此规则的三种例外情况:

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

运行结果如下图所示。

 C#中的线程池使用(二)-LMLPHP

图  线程池的应用结果

 注意:在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());
        }
    }
}

运行结果如下图所示。

 C#中的线程池使用(二)-LMLPHP

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

        }
    }
}

运行结果如下图所示。

 C#中的线程池使用(二)-LMLPHP

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

}
12-25 11:16