一般情况下我们都使用Thread类创建线程,因为通过Thread对象可以对线程进行灵活 的控制。但过多创建线程和销毁线程,会消耗掉大量的内存和CPU资源, 假如某段时间内突然爆发了100个短小的线程,创建和销毁这些线程就会消耗很多时间, 可能比线程本身运行的时间还长。为了改善这种状况,.NET提供了一种称之为线程池 (Thread Pool)的技术。线程池提供若干个固定线程轮流为大量的任务服务,比如用10个 线程轮流执行100个任务,当一个线程完成任务时,并不马上销毁,而是接手另一个任务, 从而减少创建和销毁线程的消耗。 线程池由 System.Threading 命名空间中的 ThreadPool 类实现,其部分方法如表 所示。
ThreadPool 是一个静态类,不必创建实例就可以使用它。一个应用程序最多只有一个 线程池,它会在首次向线程池中排入工作函数时自动创建。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace XianChengChi
{
internal class Program
{
static void Main(string[] args)
{
ThreadPoolTest();
Console.ReadKey(); //按下任意键结束程序
}
public static void ThreadPoolTest()
{
//向线程池中添加100个工作线程
for (int i = 1; i <= 100; i++)
{//ThreadPool.QueueUserWorkItem 是调用线程池来排队工作项的方法//WaitCallback 是一个委托类型,它指向一个没有返回值并接受一个 object 类型参数的方法。
ThreadPool.QueueUserWorkItem(new WaitCallback(WorkFunction), i);//当 WorkFunction 被调用时,参数i会被作为 object 类型的参数传入
}//创建了一个新的 WaitCallback 委托实例,它指向 WorkFunction 方法
}
//工作函数
public static void WorkFunction(object n)
{
Console.Write(n + "\t");
}
}
}
启动程序:
当需要处理返回值或执行异步操作时,应该考虑使用 Task
或 async/await
关键字
创建一个返回 Task<TResult>
的方法,其中 TResult
是你期望的返回类型。然后,你可以使用 Task.Run
方法来在线程池上异步执行这个任务,并等待它完成以获取结果。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace XianChengYiBu
{
internal class Program
{
static async Task Main(string[] args)
{
string n = "Hello";
int i = 123;
// 使用 Task.Run 和 lambda 表达式来在线程池上异步执行 WorkFunctionAsync
Task<int> task = Task.Run(() => WorkFunctionAsync(n, i));
// 等待任务完成并获取结果
int result = await task;
// int result = await WorkFunctionAsync("SomeString", 123);// 调用 WorkFunctionAsync 并等待结果
Console.WriteLine("Result: " + result);
Console.ReadKey();
}
//----------------------- ------------------------------//
public static async Task<int> WorkFunctionAsync(string n, int i)//Task<int>,int是返回值
{
// 模拟一些工作
Console.Write(n + "\t" + i + "\n");
// 假设这是某个计算的结果
int result = i * 2;
return result;
}
}
}
启动程序:
ThreadPool 可以看做容纳线程的容器,我们可通过QueueUserWorkItem()方法把工作函 数排入线程池。
在上面的程序中,我们向线程池中排入了100个工作函数,线程池分别独立的完成了 这100个任务。下面我们研究一下 线程池运行过程中线程数目的变化情况,从而加深对线程池的理解。为了叙述方便,我们 假设下限为10,上限为30。
1.当线程池被创建后,里面就会创建10个空线程(和下限值相同)。
2.当我们向线程池中排入一个任务后,就会有一个空线程接手该任务,然后运行起
来。随着我们不断向线程池中排入任务,线程池中的空线程逐一运行起来。
3.随着任务的不断增加,在某一时刻任务数量会超出下限,这时线程的数量就不够
用了,但线程池并不会立即创建新线程,而是等待500毫秒左右,这么做的目的是看看在
这段时间内是否有其他线程完成任务并接手这个请求,这样就可以避免因创建新线程而造
成的消耗。如果这段时间内没有线程完成任务,就创建一个新线程去执行新任务。
4.在任务数量超过下限后,随着新任务的不断排入,线程池中线程数量持续增加,
直至达到上限值为止。
5.当线程数量达到上限时,继续增加任务,线程数量将不再增加。比如你向线程池
中排入100个任务,则只有30个进入线程池(和上限相同),另外70个在线程池外排队
等待。当线程池中的某个线程完成任务后,并不会立即终止,而是从等待队列中选择一个
任务继续执行,这样就减少了因创建和销毁线程而消耗的时间。
6.随着任务逐步完成,线程池外部等候的任务被逐步调入线程池,任务的数量逐步
减少,但线程的总数保持恒定,始终为30(和上限值相同)。
7.随着任务的逐渐减少,总有某一时刻,任务数量会小于上限值,这时线程池内多
余的线程会在空闲2分钟后被释放并回收相关资源。线程数目逐步减少,直到达到下限值
为止。
8.当任务数量减小到下限值之下时,线程池中的线程数目保持不变(始终和下限值
相同),其中一部分在执行任务,另一部分处于空运行状态。
9.当所有任务都完成后,线程池恢复初始状态,运行10个空线程。
由上面的论述可以看出线程池提高效率的关键是一个线程完成任务后可以继续为其
他任务服务,这样就可以使用有限的几个固定线程轮流为大量的任务服务,从而减少了因
频繁创建和销毁线程所造成的消耗。
可见线程池适用于包含包含大量简单线程且这些线程不需要特殊控制的程序,它不但
简单快捷,而且所耗费的系统开销远比Thread少。
ThreadPool 中的线程不用手动开始,也不能手动取消,你要做的只是把工作函数排入
线程池,剩下的工作将由系统自动完成,也就是说我们不能控制线程池中的线程。如果想
对线程进行更多的控制,那么就不适合使用线程池。在以下情况中不宜使用 ThreadPool
类而应该使用单独的Thread类:
1. 线程执行需要很长时间(如果有些线程长期占用线程池,那么对在外面排队的任
务说就是灾难);
2. 需要为线程指定详细的优先级;
3. 在执行过程中需要对线程进行操作,比如睡眠,挂起等。
所以ThreadPool 适合于并发运行若干个运行时间不长且互不干扰的函数。