许多应用程序创建的线程都要在休眠状态中消耗大量时间,以等待事件发生。其他线程可能进入休眠状态,只被定期唤醒以轮询更改或更新状态信息。线程池通过为应用程序提供一个由系统管理的辅助线程池使您可以更为有效地使用线程。一个线程监视排到线程池的若干个等待操作的状态。当一个等待操作完成时,线程池中的一个辅助线程就会执行对应的回调函数。
托管线程池中的线程为后台线程,即它们的 IsBackground 属性为 true。这意味着在所有的前台线程都已退出后,
ThreadPool 线程不会让应用程序保持运行。
每个进程都有一个线程池。线程池的默认大小为每个可用处理器有 25 个线程。使用 SetMaxThreads 方法可以更改线程池中的线程数。每个线程使用默认的堆栈大小并按照默认的优先级运行。
1.如何使用线程池
ThreadPool提供了几个静态方法,来允许使用者插入一个工作线程的需求。
public static bool QueueUserWorkItem (WaitCallback callBack);
public static bool QueueUserWorkItem (WaitCallback callBack,Object state);
public static bool UnsafeQueueUserWorkItem (WaitCallback callBack,Object state);
public static RegisteredWaitHandle RegisterWaitForSingleObject (
WaitHandle waitObject,
WaitOrTimerCallback callBack,
Object state,
int millisecondsTimeOutInterval,
bool executeOnlyOnce
)
参数解释:
WaitCallback委托:表示线程池线程要执行的回调方法。
委托声明:public delegate void WaitCallback(Object state);
WaitOrTimerCallback 委托:表示当 WaitHandle 超时或终止时要调用的方法。
委托声明: public delegate void WaitOrTimerCallback (Object state,bool timedOut)
WaitHandle:封装等待对共享资源的独占访问的操作系统特定的对象。
state:要传给委托的对象。
millisecondsTimeOutInterval:以毫秒为单位的超时。如果 millisecondsTimeOutInterval 参数为零 (0),则函数
测试对象的状态并立即返回。如果 millisecondsTimeOutInterval 为 -1,则函数的超时间隔永远不过期。
executeOnlyOnce:如果为 true,表示在调用了委托后,线程将不再在 waitObject 参数上等待;如果为 false,表示每次完成等待操作后都重置计时器,直到注销等待。
1.1 QueueUserWorkItem的使用
代码1:
1 using System; 2 using System.Threading; 3 4 public class Example 5 { 6 static void Main() 7 { 8 WaitHandle[] waitHandles = new WaitHandle[] { new AutoResetEvent(false), new AutoResetEvent 9 10 (false) }; 11 ThreadPool.QueueUserWorkItem(new WaitCallback((action) => 12 { 13 AutoResetEvent ae = (AutoResetEvent)action; 14 Console.WriteLine("Work1 is starting......."); 15 Thread.Sleep(2000); 16 Console.WriteLine("Work1 is completed......"); 17 ae.Set(); 18 }), waitHandles[0]); 19 ThreadPool.QueueUserWorkItem(new WaitCallback((action) => 20 { 21 AutoResetEvent ae = (AutoResetEvent)action; 22 Console.WriteLine("Work2 is Starting......"); 23 Thread.Sleep(1000); 24 Console.WriteLine("Work2 is completed......"); 25 ae.Set(); 26 }), waitHandles[1]); 27 WaitHandle.WaitAll(waitHandles); 28 Console.WriteLine("Two work completed"); 29 Console.ReadKey(); 30 } 31 }
结果:
Work1 is starting......
Work2 is Starting......
Work2 is completed......
Work1 is completed......
Two work completed
代码只演示了QueueUserWorkItem方法,事实上Unsafe QueueWorkItem实现了完全相同的功能。两者的差别在于,
UnsafeQueueWorkItem方法不会把调用线程的堆栈传递给辅助线程,这意味着主线程的权限限制不会传递给辅助线程。
使用 UnsafeQueueUserWorkItem 可能会无意中打开一个安全漏洞。代码访问安全性的权限检查基于所有调用方对堆
栈的权限进行。如果使用 UnsafeQueueUserWorkItem 将工作排在某个线程池线程上,则该线程池线程的堆栈将不会具
有实际调用方的背景。恶意代码可能会利用这一点避开权限检查。
1.2 RegisterWaitForSingleObject的使用
RegisterWaitForSingleObject可以项Timer控件一样,每隔一段时间执行一次方法。WaitHandle来触发执行方法。
继承WaitHandle有Mutex,ManualResetEvent,AutoResetEvent,一般我们使用后两个.
ManualResetEvent和AutoResetEvent的区别在于:前者调用Set方法后将自动将值将一直保持为true,后者调用Set方法将变为true随后立即变为false,可以将它理解为一个脉冲。
代码2:
1 public class Example 2 { 3 public static void Main(string[] args) 4 { 5 AutoResetEvent ev = new AutoResetEvent(false); 6 7 TaskInfo ti = new TaskInfo(); 8 ti.OtherInfo = "First task"; 9 ti.Handle = ThreadPool.RegisterWaitForSingleObject( 10 ev, 11 new WaitOrTimerCallback(WaitProc), 12 ti, 13 1000, 14 false 15 ); 16 Thread.Sleep(3100); 17 Console.WriteLine("Main thread signals."); 18 ev.Set(); 19 Console.ReadKey(); 20 } 21 22 public class TaskInfo 23 { 24 public RegisteredWaitHandle Handle = null; 25 public string OtherInfo = "default"; 26 } 27 28 public static void WaitProc(object state, bool timedOut) 29 { 30 TaskInfo ti = (TaskInfo)state; 31 32 string cause = "TIMED OUT"; 33 if (!timedOut) 34 { 35 cause = "SIGNALED"; 36 if (ti.Handle != null) 37 ti.Handle.Unregister(null); 38 } 39 40 Console.WriteLine("WaitProc( {0} ) executes on thread {1}; cause = {2}.", 41 ti.OtherInfo, 42 Thread.CurrentThread.GetHashCode().ToString(), 43 cause 44 ); 45 } 46 }
结果:
WaitProc( First task ) executes on thread 12; cause = TIMED OUT.
WaitProc( First task ) executes on thread 12; cause = TIMED OUT.
WaitProc( First task ) executes on thread 12; cause = TIMED OUT.
Main thread signals.
WaitProc( First task ) executes on thread 12; cause = SIGNALED.
2.如何查看和设置线程池的上下限
线程池作为一个缓冲池,有着其上限和下限。在通常情况下,当线程池中的线程数小于线程池设置的下限时,线程
池会设法创建新的线程,而当线程池中的线程数大于线程池设置的上限时,线程池将销毁多余线程。简单来说,线程池扮演着一个拥有上下阈值的容器,它能够保证线程池中的线程数量维持在大于最小值和小于最大值的区域中。
在.NET 2.0之后,每个CPU默认的工作者线程最大值为25个,最小值为2个,而IO线程的默认最大值是1000个,最小为2个。
在通常情况下,程序员无须修改默认的配置,但在一些场合,程序员可能需要了解线程池的上下限和剩余的线程数。
ThreadPool类提供了5个方法:
void ThreadPool.GetMaxThreads(out int workThreads, out int completionPortThreads);
workerThreads 线程池中辅助线程的最大数目。
completionPortThreads 线程池中异步 I/O 线程的最大数目
void ThreadPool.GetMinThreads(out int workThreads, out int completionPortThreads);
workerThreads 当前由线程池维护的空闲辅助线程的最小数目。
completionPortThreads 线程池中异步 I/O 线程的最小数目
bool ThreadPool.SetMaxThreads(int workThreads, int completionPortThreads);
workerThreads 线程池中辅助线程的最大数目。
completionPortThreads 线程池中异步 I/O 线程的最大数目。
bool ThreadPool.SetMinThreads(int workThreads, int completionPortThreads);
workerThreads 要由线程池维护的新的最小空闲辅助线程数。
completionPortThreads 要由线程池维护的新的最小空闲异步 I/O 线程数。
void ThreadPool.GetAvailableThreads(out int workThreads, out int completion PortThreads);
workerThreads 可用辅助线程的数目。
completionPortThreads 可用异步 I/O 线程的数目。