在多线程的程序中,经常会出现两种情况。一种情况下,应用程序中的线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应;而另外一种情况则是线程平常都处于休眠状态,只是周期性地被唤醒。这里分析及介绍.Net Framework中ThreadPool class来对付第一种情况,相应地也会谈到QueueUserWorkItem方法和WaitCallback委托。而使用Timer(System.Threading.Timer or System.Windows.Forms.Timer)来对付第二种情况,可以参考《System.Threading.Timer类的TimerCallback 委托》。
1. ThreadPool介绍(From MSDN)
ThreadPool class提供了一个线程池,该线程池可用于发送工作项、处理异步 I/O、代表其他线程等待以及处理计时器。线程池允许在后台运行多个工作,而不需要为每个任务频繁地创建和销毁单独的线程,从而减少了开销。
线程池通过为应用程序提供一个由系统管理的辅助线程池使您可以更为有效地使用线程。一个线程监视排到线程池的若干个等待操作的状态。当一个等待操作完成时,线程池中的一个辅助线程就会执行对应的回调函数。
也可以将与等待操作不相关的工作项排列到线程池。若要请求由线程池中的一个线程来处理工作项,请调用 QueueUserWorkItem 方法。此方法将对将被从线程池中选定的线程调用的方法或委托的引用用作参数。一个工作项排入队列后就无法再取消它。
计时器(System.Threading.Timer)队列中的计时器以及已注册的等待操作也使用线程池。它们的回调函数也会排列到线程池。关于这部分的内容,可以参考《System.Threading.Timer类的TimerCallback 委托》。
线程池在首次创建 ThreadPool 类的实例时被创建。线程池具有每个可用处理器 25 个线程的默认限制,这可以使用 mscoree.h 文件中定义的 CorSetMaxThreads 来更改。每个线程使用默认的堆栈大小并按照默认的优先级运行。每个进程只能具有一个操作系统线程池。
2. ThreadPool.QueueUserWorkItem方法
QueueUserWorkItem方法将指定的方法排入队列以便执行,并指定包含该方法所用数据的对象,此方法在有线程池线程变得可用时执行。
public static bool QueueUserWorkItem(
WaitCallback callBack,
object state
);
如果将方法成功排入队列,则为 true;否则为 false。如果排入队列的方法仅需要单个数据项,可以将数据项强制转换为类型 Object。如果该方法需要多个复杂数据,则必须定义包含这些数据的类。如果没有参数传入,则可以调用QueueUserWorkItem(WaitCallback callBack)重载。
ThreadPool提供的公共方法都是static方法,因此也不需要生成ThreadPool对象。通过QueueUserWorkItem方法在线程池中添加一个工作项目后,目前没有提供简单的方法取消。你不必建立咨监线程,只需要把相应的函数或方法依托WaitCallback委托传递给ThreadPool.QueueUserWorkItem()方法即可。而线程的创建、管理和运行等等都由系统自动完成,这就是ThreadPool的优点。
线程池总是通过CLR使用多线程单元模式,借助抢先式多任务方式,利用高性能的队列和调度程序来实现的。在这个过程中,CPU时间被分成多个时间片。在每个时间片中,都在执行一个线程,而其他线程则处于等待状态。这个时间片用完之后,系统就根据剩余的最高优先级决定由哪个线程使用CPU。客户请求排在任务队列中,队列中的每个任务都分配给线程池中第一个可用的线程。
一旦线程完成了分配给它的任务,就返回线程池,等待CLR的下一次分配。线程池的大小可以是固定不变的,也可以是动态变化的。通常情况下,如果确切知道应用程序可用资源的数量,就可以使用这种类型的线程池,这样固定数目的线程就可以在线程池初始化过程中创建。例如在Web服务器中,我们不知道将要同时处理多少个客户请求。
3. WaitCallback委托
WaitCallback委托声明线程池要执行的回调方法,回调方法的声明必须与WaitCallback委托声明具有相同的参数。
WaitCallback 表示要在 ThreadPool 线程上执行的回调方法。创建委托,方法是将回调方法传递给 WaitCallback 构造函数。您的方法必须具有此处所显示的签名。通过将 WaitCallback 委托传递给 ThreadPool.QueueUserWorkItem 来将任务排入队列以便执行。您的回调方法将在某个线程池线程可用时执行。
如果要将信息传递给回调方法,请创建包含所需信息的对象,并在将任务排入队列以便执行时将它传递给 QueueUserWorkItem。每次执行您的回调方法时,state 参数都包含此对象。
通过将一个方法打包到 WaitCallback 委托中,然后将该委托传递给 ThreadPool.QueueUserWorkItem 静态方法,在线程池中对任务进行排队。
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace Prog
{
class ThreadPoolDemo
{
public void LongTask1( object obj)
{
for ( int i = 0 ; i <= 999 ; i ++ )
{
Console.WriteLine( " Long Task1 is being executed " );
}
}
public void LongTask2( object obj)
{
for ( int i = 0 ; i <= 999 ; i ++ )
{
Console.WriteLine( " Long Task2 is being executed " );
}
}
static void Main( string [] args)
{
ThreadPoolDemo tpd = new ThreadPoolDemo();
for ( int i = 0 ; i < 50 ; i ++ )
{
ThreadPool.QueueUserWorkItem( new WaitCallback(tpd.LongTask1));
ThreadPool.QueueUserWorkItem( new WaitCallback(tpd.LongTask2));
}
Console.Read();
}
}
}
在这个例子中,它包括两个任务LongTask1和LongTask2,这两个任务相当简单,只是在循环中向控件台输出一条消息。
ThreadPool类可以用来启动这两个任务。而不用为每个任务设置线程属性,其方法是向WaitCallback()方法传递过程的委托。如下所示:
ThreadPool.QueueUserWorkItem(new WaitCallback(tpd.LongTask2));
ThreadPool.QueueUserWorkItem(new WaitCallback(tpd.LongTask2));
注意在ThreadPool类中,QueryUserWorkItem是一个静态方法,因此可以由ThreadPool类直接调用 。
向线程池中的线程传递数据并从中得到返回值。
线程池架构只允许传递一个对象参数,但我们常常希望给线程池中的线程所执行的方法传递好几个参数。可以把所有的参数和都包装到一个类中,然后向QueueUserWorkItem()方法传递该类的一个实例作为参数:
using System.Threading;
using System.Runtime.CompilerServices;
public class ClassForMain
{
class ObjState
{
protected internal String inarg1;
protected internal String inarg2;
protected internal String outval;
}
class ThreadPoolState
{
public void Task1(object stateObj)
{
ObjState StObj;
StObj= (ObjState)stateObj;
Console.WriteLine("Input Argument 1 in task 1:" + StObj.inarg1);
Console.WriteLine("Input Argument 2 in task 1:" + StObj.inarg2);
StObj.outval = "From Task1" + StObj.inarg1 + " " + StObj.inarg2;
}
public void Task2(object stateObj)
{
ObjState StObj;
StObj = (ObjState)stateObj;
Console.WriteLine("Input Argument1 in task 2:" + StObj.inarg1);
Console.WriteLine("Input Argument2 in task 2:" + StObj.inarg2);
StObj.outval = "From Task2" + StObj.inarg1 + " " + StObj.inarg2;
}
}
public static void Main(string[] args)
{
ObjState StObj1 = new ObjState();
ObjState StObj2 = new ObjState();
//给参数赋值
StObj1.inarg1 = "String Param1 of task 1";
StObj1.inarg2 = "String Param2 of task 1";
StObj2.inarg1 = "String Param1 of task2";
StObj2.inarg2 = "String Param2 of task2";
ThreadPoolState tps = new ThreadPoolState();
//将任务项排列到线程池中,指定任务执行时要传递给委托的对象为StObj1
ThreadPool.QueueUserWorkItem(new WaitCallback(tps.Task1), StObj1);
//将任务项排列到线程池中,指定任务执行时要传递给委托的对象为StObj2
ThreadPool.QueueUserWorkItem(new WaitCallback(tps.Task2),StObj2);
Console.Read();
}
}
这个示例与前面示例很相似。但它传递的是一个对象,我们使用ObjState对象向在线程池中排队的任务传递输入和输出参数 。
ObjState对象包含两个输入参数和和一个输出参数,所有参数都是字符型,如下所示:
class ObjState
{
protected internal String inarg1;
protected internal String inarg2;
protected internal String outval;
}
下面定义两个方法task1和task2,向每个过程传递一个ObjState类的实例作为其参数。方法task1和task2将所有传递的ObjState对象中的两个输入参数 inarg1和 inarg2的值保存起来,并将结果保存在outval类变量中。
public void Task1(object stateObj)
{
ObjState StObj;
StObj= (ObjState)stateObj;
Console.WriteLine("Input Argument 1 in task 1:" + StObj.inarg1);
Console.WriteLine("Input Argument 2 in task 1:" + StObj.inarg2);
StObj.outval = "From Task1" + StObj.inarg1 + " " + StObj.inarg2;
}
public void Task2(object stateObj)
{
ObjState StObj;
StObj = (ObjState)stateObj;
Console.WriteLine("Input Argument1 in task 2:" + StObj.inarg1);
Console.WriteLine("Input Argument2 in task 2:" + StObj.inarg2);
StObj.outval = "From Task2" + StObj.inarg1 + " " + StObj.inarg2;
}
在Main()方法中,使用ThreadPool类的QueueUserWorkItem()将这两个任务排列在线程池中,其代码如下 :
public static void Main(string[] args)
{
ObjState StObj1 = new ObjState();
ObjState StObj2 = new ObjState();
//给参数赋值
StObj1.inarg1 = "String Param1 of task 1";
StObj1.inarg2 = "String Param2 of task 1";
StObj2.inarg1 = "String Param1 of task2";
StObj2.inarg2 = "String Param2 of task2";
ThreadPoolState tps = new ThreadPoolState();
//将任务项排列到线程池中,指定任务执行时要传递给委托的对象为StObj1
ThreadPool.QueueUserWorkItem(new WaitCallback(tps.Task1), StObj1);
//将任务项排列到线程池中,指定任务执行时要传递给委托的对象为StObj2
ThreadPool.QueueUserWorkItem(new WaitCallback(tps.Task2),StObj2);
Console.Read();
}
使用RegisterWaitForSingleObject方法(给它传递WaitHandle参数)也可以将拥有等待操作的任务项排列到线程池中。 使用方法是注册一个等待 WaitHandle 的委托,并指定一个 32 位有符号整数来表示超时时间(以毫秒为单位)。原型如下:
public static RegisteredWaitHandle RegisterWaitForSingleObject (这个waitHandle包装在WaitOrTimerCallBack委托中的方法发出信号。
WaitHandle waitObject,
WaitOrTimerCallback callBack,
Object state,
int millisecondsTimeOutInterval,
bool executeOnlyOnce
)
使用 AutoResetEvent 发出信号,通知执行任务。
使用线程池,你需要注册一个连同将被执行的委托的Wait Handle,在Wait Handle发信号时。这个工作通过调用ThreadPool.RegisterWaitForSingleObject来完成,如下:
using System;
using System.Threading;
using System.Runtime.CompilerServices;
public class ClassForMain
{
private static int i = 0;
public static void Main(string[] args)
{
AutoResetEvent argv = new AutoResetEvent(false);
ThreadPool.RegisterWaitForSingleObject(argv, new WaitOrTimerCallback(workitem), null, 2000, false);
argv.Set();
Console.Read();
}
public static void workitem(object O, bool signaled)
{
i += 1;
Console.WriteLine("Thread Pool Work Item Invoked:" + i.ToString());
}
}
程序每两秒钟输出一个新行,i值递增1,直到用户按下Enter键。
首先,创建一个AutoResetEvent对象argv,其初始状态是没有发出执行所排列的任务项的信号:
AutoResetEvent argv = new AutoResetEvent(false);
调用RegisterWaitForSingleObject()方法,其状态参数为null,将超时值设置为2000毫秒。ResisgerWairForSingleObject()方法注册了一个委托,并以特定的时间间隔通知任务项。在示例中,时间被设置为2秒,如下面的代码所示:
ThreadPool.RegisterWaitForSingleObject(argv, new WaitOrTimerCallback(workitem), null, 2000, false);
为了引发事件,需要使用AutoResetEvent对象的Set()方法:
arev.set();
另一个相关的例子:
// information, and pass that object to a task queued for
// execution by the thread pool.
using System;
using System.Threading;
class Test
{
static ManualResetEvent starter = new ManualResetEvent(false);
public static void Main()
{
ThreadPool.RegisterWaitForSingleObject(starter, Go, "hello", -1, true);
Thread.Sleep(5000);
Console.WriteLine("Signaling worker...");
starter.Set();
Console.ReadLine();
}
public static void Go(object data, bool timedOut)
{
Console.WriteLine("Started " + data);
// 完成任务...
}
}