在多线程编程中,线程的创建和销毁是非常消耗系统资源的,因此,C#引入了池的概念,类似的还有数据库连接池,这样,维护一个池,池内维护的一些线程,需要的时候从池中取出来,不需要的时候放回去,这样就避免了重复创建和销毁线程。

ThreadPool类 MSDN帮助信息: http://msdn.microsoft.com/zh-cn/library/system.threading.threadpool.aspx#Y0

将任务添加进线程池:

ThreadPool.QueueUserWorkItem(new WaitCallback((方法名));
ThreadPool.QueueUserWorkItem(new WaitCallback((方法名),传入方法的参数);

对线程池的线程数量进行控制

SetMaxThreads(Int32, Int32) //设置可以同时处于活动状态的线程池的请求数目。所有大于此数目的请求将保持排队状态,直到线程池线程变为可用。

SetMinThreads(Int32, Int32) //发出新的请求时,在切换到管理线程创建和销毁的算法之前设置线程池按需创建的线程的最小数量。

对线程池线程数量控制的验证

public static void Main()
{
ThreadPool.SetMinThreads(, );
ThreadPool.SetMaxThreads(, ); for (int i = ; i < ; i++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback((object obj) => { Console.WriteLine("当前值为:" + obj + "线程ID" + Thread.CurrentThread.ManagedThreadId); }),i);
} Console.Read();
}

最大线程数量和最小线程数量全部设置为1,上述代码的执行结果为:

可以看到只开启了一个线程。将最大线程改为2

public static void Main()
{
ThreadPool.SetMinThreads(, );
ThreadPool.SetMaxThreads(, ); for (int i = ; i < ; i++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback((object obj) => { Console.WriteLine("当前值为:" + obj + "线程ID" + Thread.CurrentThread.ManagedThreadId); }),i);
} Console.Read();
}

此时启动了两个线程

但是这最多和最少并不是说一定要使用这么多线程的,比如,我设置最少10个线程,但是实际上可能只试用了3-4个,但是线程池中确实是最少会维护着10个线程,不一定每次全部都启用的。

public static void Main()
{
ThreadPool.SetMinThreads(, );
ThreadPool.SetMaxThreads(, ); Console.WriteLine("测试开始!"); for (int i = ; i < ; i++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback((object obj) => {
Console.WriteLine("当前值为:" + obj + "线程ID" + Thread.CurrentThread.ManagedThreadId); }),i);
} Console.WriteLine("测试结束!"); Console.Read();
}

上面的执行结果:

为什么打印测试结束的语句执行的这么靠前呢?这是什么原因呢?

这是因为在循环中将任务添加到线程池中后,并没有等待线程执行完成再继续执行主线程,也就是线程池中的现成是如何启动及结束我们是不知道的,ThreadPool没有提供简单的方法来获取工作线程是否已经结束,所以需要通过其他方法实现。此时,需要引入ManualResetEvent类,MSDN:https://msdn.microsoft.com/zh-cn/library/system.threading.manualresetevent.aspx

ManualResetEvent 允许线程通过发信号互相通信。通常,此通信涉及一个线程在其他线程进行之前必须完成的任务。

当一个线程开始一个活动(此活动必须完成后,其他线程才能开始)时,它调用 Reset 以将 ManualResetEvent 置于非终止状态。此线程可被视为控制 ManualResetEvent。调用 ManualResetEvent 上的 WaitOne 的线程将阻止,并等待信号。当控制线程完成活动时,它调用 Set 以发出等待线程可以继续进行的信号。并释放所有等待线程。

一旦它被终止,ManualResetEvent 将保持终止状态,直到它被手动重置。即对 WaitOne 的调用将立即返回。

可以通过将布尔值传递给构造函数来控制 ManualResetEvent 的初始状态,如果初始状态处于终止状态,为 true;否则为 false。

方法WaitOne(Timeout.Infinite, true);  阻止当前线程,直到当前 WaitHandle 收到信号为止。

方法Set(); 将事件状态设置为终止状态,允许一个或多个等待线程继续。

这段话到底是什么意思呢?我们通过一段代码来理解

public static void Main()
{
ThreadPool.SetMinThreads(,); ManualResetEvent mre = new ManualResetEvent(false); for (int i = ; i < ; i++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback((object obj) =>
{
Console.WriteLine("线程 " + obj.ToString() + " 已启动! 线程ID为:" + Thread.CurrentThread.ManagedThreadId ); mre.WaitOne(); Thread.Sleep(); Console.WriteLine("线程 " + obj.ToString() + " 已结束!

线程ID为:"

 + Thread.CurrentThread.ManagedThreadId );

        }), i);
} Console.Read();
}

上述代码的执行结果是:

从执行结果中可以看到,我们往线程池中添加了三个任务,线程池启用了三个线程去执行。当任务方法执行到mre.WaitOne();时,线程被ManualResetEvent 阻止,并没有继续往下走,也就是此时线程们正在等待一个信号,下面我们就给线程们发出这个信号。

public static void Main()
{
ThreadPool.SetMinThreads(,); ManualResetEvent mre = new ManualResetEvent(false); for (int i = ; i < ; i++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback((object obj) =>
{
Console.WriteLine("线程 " + obj.ToString() + " 已启动! 线程ID为:" + Thread.CurrentThread.ManagedThreadId ); mre.WaitOne(); Thread.Sleep(); Console.WriteLine("线程 " + obj.ToString() + " 已结束!线程ID为:" + Thread.CurrentThread.ManagedThreadId); }), i);
} if (Console.ReadLine() == "go")
{
mre.Set();
} Console.Read();
}

三个新线程虽然被阻止,但是主线程是可以继续执行的,当主线程收到用户输入的go命令时,给三个线程发送信号,线程们收到信号后继续执行,并打印出执行结束的标识。

相信到这里我们应该能够理解WaitOne和Set的用法了,下面我们在看看Reset方法,我们在mre.Set()后,在开启三个新的线程

public static void Main()
{
ThreadPool.SetMinThreads(,); ManualResetEvent mre = new ManualResetEvent(false); for (int i = ; i < ; i++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback((object obj) =>
{
Console.WriteLine("线程 " + obj.ToString() + " 已启动! 线程ID为:" + Thread.CurrentThread.ManagedThreadId ); mre.WaitOne(); Thread.Sleep(); Console.WriteLine("线程 " + obj.ToString() + " 已结束!线程ID为:" + Thread.CurrentThread.ManagedThreadId); }), i);
} if (Console.ReadLine() == "go")
{
mre.Set();
} Thread.Sleep(); Console.WriteLine("*******************************再开启三个线程***********************************"); for (int i = ; i < ; i++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback((object obj) =>
{
Console.WriteLine("线程 " + obj.ToString() + " 已启动! 线程ID为:" + Thread.CurrentThread.ManagedThreadId); mre.WaitOne(); Thread.Sleep(); Console.WriteLine("线程 " + obj.ToString() + " 已结束!线程ID为:" + Thread.CurrentThread.ManagedThreadId); }), i);
} Console.Read();
}

运行代码看看效果

新开的线程和之前开的线程的任务方法是一模一样的啊,为什么没有等待信号而直接继续运行了呢?

这是因为我们在调用ManualResetEvent的Set方法后,在调用其 Reset 方法前会一直保持终止状态,所以,新线程任务方法中的WaitOne是无效的,因为此时ManualResetEvent是终止状态的。下面我们加上Reset方法看看效果

public static void Main()
{
ThreadPool.SetMinThreads(,); ManualResetEvent mre = new ManualResetEvent(false); for (int i = ; i < ; i++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback((object obj) =>
{
Console.WriteLine("线程 " + obj.ToString() + " 已启动! 线程ID为:" + Thread.CurrentThread.ManagedThreadId ); mre.WaitOne(); Thread.Sleep(); Console.WriteLine("线程 " + obj.ToString() + " 已结束!线程ID为:" + Thread.CurrentThread.ManagedThreadId); }), i);
} if (Console.ReadLine() == "go")
{
mre.Set();
} Thread.Sleep(); Console.WriteLine("*******************************再开启三个线程***********************************"); mre.Reset(); for (int i = ; i < ; i++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback((object obj) =>
{
Console.WriteLine("线程 " + obj.ToString() + " 已启动! 线程ID为:" + Thread.CurrentThread.ManagedThreadId); mre.WaitOne(); Thread.Sleep(); Console.WriteLine("线程 " + obj.ToString() + " 已结束!线程ID为:" + Thread.CurrentThread.ManagedThreadId); }), i);
} if (Console.ReadLine() == "go")
{
mre.Set();
} Console.Read();
}

执行结果:

此时,达到了我们想要的结果。

最后调用 mre.Close();释放资源即可。

现在明白了ManualResetEvent类的使用,想要解决开始的“测试开始、测试结束”打印顺序问题就是张飞吃豆芽了吧?!

C#线程池ThreadPool的理解的更多相关文章

  1. android线程池ThreadPoolExecutor的理解

    android线程池ThreadPoolExecutor的理解 线程池 我自己理解看来.线程池顾名思义就是一个容器的意思,容纳的就是ThreadorRunable, 注意:每一个线程都是需要CPU分配 ...

  2. 线程池ThreadPool的常用方法介绍

    线程池ThreadPool的常用方法介绍 如果您理解了线程池目的及优点后,让我们温故下线程池的常用的几个方法: 1. public static Boolean QueueUserWorkItem(W ...

  3. python中多进程multiprocessing、多线程threading、线程池threadpool

    浅显点理解:进程就是一个程序,里面的线程就是用来干活的,,,进程大,线程小 一.多线程threading 简单的单线程和多线程运行:一个参数时,后面要加逗号 步骤:for循环,相当于多个线程——t=t ...

  4. 线程池ThreadPool的初探

    一.线程池的适用范围 在日常使用多线程开发的时候,一般都构造一个Thread示例,然后调用Start使之执行.如果一个线程它大部分时间花费在等待某个事件响应的发生然后才予以响应:或者如果在一定期间内重 ...

  5. C#多线程学习 之 线程池[ThreadPool](转)

    在多线程的程序中,经常会出现两种情况: 一种情况:   应用程序中,线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应                   这一般使用ThreadPo ...

  6. 高效线程池(threadpool)的实现

    高效线程池(threadpool)的实现 Nodejs编程是全异步的,这就意味着我们不必每次都阻塞等待该次操作的结果,而事件完成(就绪)时会主动回调通知我们.在网络编程中,一般都是基于Reactor线 ...

  7. 多线程系列 线程池ThreadPool

    上一篇文章我们总结了多线程最基础的知识点Thread,我们知道了如何开启一个新的异步线程去做一些事情.可是当我们要开启很多线程的时候,如果仍然使用Thread我们需要去管理每一个线程的启动,挂起和终止 ...

  8. C# -- 使用线程池 ThreadPool 执行多线程任务

    C# -- 使用线程池 ThreadPool 执行多线程任务 1. 使用线程池 class Program { static void Main(string[] args) { WaitCallba ...

  9. 多线程Thread,线程池ThreadPool

    首先我们先增加一个公用方法DoSomethingLong(string name),这个方法下面的举例中都有可能用到 #region Private Method /// <summary> ...

随机推荐

  1. 加载gif图过渡效果

    加载gif图片,过渡效果: 调用: - (id)initWithGifView:(UIView *)view { self = [super initWithView:view]; if (self) ...

  2. hdu5358 First One(尺取法)

    转载请注明出处: http://www.cnblogs.com/fraud/          ——by fraud First One Time Limit: 4000/2000 MS (Java/ ...

  3. hdu5323 Solve this interesting problem(爆搜)

    转载请注明出处: http://www.cnblogs.com/fraud/          ——by fraud Solve this interesting problem Time Limit ...

  4. 利用程序将.jrxml导出为.jasper文件并用PDF显示(2)

    直接在Web项目中使用.jasper文件,其实在软件项目开发时,.jrxml文件经常修改,而且.jrxml还要在项目中进行保留备份,所以较为流行的做法是在项目中使用.jrxml来生成.jasper文件 ...

  5. Android SDCard和内部存储中gcc编译后的可执行文件无法运行提示 cannot execute - Permission denied

    原因是mount的方式问题,root后运行 su mount -o rw,remount /mnt/sdcard   //SDCard mount -o rw,remount /mnt/interna ...

  6. yii开启gii功能

    如果不想面对黑白界面,那么yii框架,给我们提供了一个模块gii 在配置文件中main.php 再通过访问模块的方式访问gii

  7. 使用Discuz!自带参数防御CC攻击以及原理,修改Discuz X 开启防CC攻击后,不影响搜索引擎收录的方法

    这部份的工作,以前花的时间太少. 希望能产生一定的作用. http://www.nigesb.com/discuz-cc-attacker-defence.html http://bbs.zb7.co ...

  8. 单片机 认识HEX文件

    看过几篇常用指令的用法后,我们换换口味,介绍一下Intel 原厂所公布的HEX文件标准格式,相信经过本文的介绍,一定可以让您对8051的操作有更进一步的认识.以下是一个程序经编译器编译后所得到的HEX ...

  9. LCS算法思想

    LCS问题就是求两个字符串最长公共子串的问题.解法就是用一个矩阵来记录两个字符串中所有位置的两个字符之间的匹配情况,若是匹配则为1,否则为0.然后求出对角线最长的1序列,其对应的位置就是最长匹配子串的 ...

  10. Android NDK R9d 安装

    NDK是一个工具集,可让您实现您的应用程序使用本机代码的语言,如C和C + +.Android NDK 是在SDK前面又加上了“原生”二字,即Native Development Kit,因此又被Go ...