多线程二:线程池(ThreadPool)

在上一篇中我们讲解了多线程的一些基本概念,并举了一些例子,在本章中我们将会讲解线程池:ThreadPool。

在开始讲解ThreadPool之前,我们先用下面的例子来回顾一下以前讲过的Thread。

 1 private void Threads_Click(object sender, EventArgs e)
 2 {
 3      Console.WriteLine($"****************btnThreads_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
 4      ThreadStart threadStart = () => this.DoSomethingLong("Threads_Click");
 5      Thread thread = new Thread(threadStart);
 6      // 启动线程
 7      thread.Start();
 8
 9      Console.WriteLine($"****************btnThreads_Click End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
10 }

上面是启动一个线程的代码,结果如下:

下面讲解一下在Thread中常见的几个方法:

1、Suspend()方法

F12查看Suspend()方法的定义:

可以看到Suspend()方法表示将线程挂起,就是将线程停止执行。上面有一个特性表示该方法已经是过时的。不建议使用该方法:因为线程挂起可能会导致死锁,线程运行的时候会占用某些资源,虽然把线程挂起了,但是资源还会占有不会释放。

2、Resume()方法

F12查看Resume()的定义:

Resume()表示唤醒挂起的线程。同样,该方法也是弃用的。

Suspend()和Resume()方法是相对的,Suspend()是挂起线程,Resume()是唤醒挂起的线程。这两个方法现在都是弃用的,不建议使用。

3、Abort()方法

F12查看定义:

从定义中可以看出:Abort()方法是终止线程,终止线程的方式是抛出异常导致线程结束。一个线程想要结束就是执行完这个线程内所有的操作,但如果这时想要结束线程,只能强制的添加一个异常,抛出异常以后使线程结束。也不建议使用Abort方法,原因有下面两点:结束线程的时候可能会有延迟;结束的时候有些操作已经发出不能终止(比如数据库增删改查的操作)。

如果一定要使用Abort()方法,必须加try-catch异常处理:

 1 try
 2 {
 3         // 销毁线程
 4         thread.Abort();
 5 }
 6 catch(Exception ex)
 7 {
 8          // 取消异常继续计算
 9          Thread.ResetAbort();
10 }

4、Join()方法

Join()方法表示线程等待,还有一个重载的方法表示等待多长时间,请看下面的例子:

 1 private void Threads_Click(object sender, EventArgs e)
 2 {
 3      Console.WriteLine($"****************btnThreads_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
 4      ThreadStart threadStart = () => this.DoSomethingLong("Threads_Click");
 5      Thread thread = new Thread(threadStart);
 6      // 启动线程
 7      thread.Start();
 8      // 线程等待
 9      thread.Join(500);// 等待500毫秒
10      Console.WriteLine("等待500毫秒");
11      thread.Join();// 当前线程等待thread完成
12      Console.WriteLine($"****************btnThreads_Click End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
13 }

结果:

5、IsBackground

IsBackground属性表示该线程是前台线程还是后台线程,默认都是前台线程。前台线程和后台线程有什么区别呢?

前台线程必须等待所有的线程都执行完之后才能退出,后台线程随着进程的退出而退出。

二、线程池ThreadPool

线程池ThreadPool是.NET框架在2.0的时候推出的,线程池可以重用线程,避免重复的创建和销毁。

1、QueueUserWorkItem()方法

QueueUserWorkItem()方法用来启动一个多线程。

1 // 启动多线程
2 ThreadPool.QueueUserWorkItem(p => this.DoSomethingLong("btnTrheadPool_Click"));

2、GetMaxThreads()方法

GetMaxThreads()用来获取线程池中最多可以有多少个辅助线程和最多有多少个异步线程。

1 ThreadPool.GetMaxThreads(out int workerThreads, out int completionPortThreads);
2 Console.WriteLine($"GetMaxThreads workerThreads={workerThreads} completionPortThreads={completionPortThreads}");

3、GetMinThreads()方法

GetMinThreads()用来获取线程池中最少可以有多少个辅助线程和最少有多少个异步线程。

1 ThreadPool.GetMinThreads(out int workerThreads, out int completionPortThreads);
2 Console.WriteLine($"GetMinThreads workerThreads={workerThreads} completionPortThreads={completionPortThreads}");

4、SetMaxThreads()和SetMinThreads()

SetMaxThreads()和SetMinThreads()分别用来设置线程池中最多线程数和最少线程数。

 1 private void btnTrheadPool_Click(object sender, EventArgs e)
 2 {
 3             Console.WriteLine($"****************btnThreadPool_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
 4             // 启动多线程
 5             ThreadPool.QueueUserWorkItem(p => this.DoSomethingLong("btnTrheadPool_Click"));
 6             Console.WriteLine("输出系统默认最多线程数和最少线程数");
 7             // 最大线程
 8             ThreadPool.GetMaxThreads(out int workerMaxThreads, out int completionPortMaxThreads);
 9             Console.WriteLine($"GetMaxThreads workerThreads={workerMaxThreads} completionPortThreads={completionPortMaxThreads}");
10
11             // 最小线程
12             ThreadPool.GetMinThreads(out int workerMinThreads, out int completionPortMinThreads);
13             Console.WriteLine($"GetMinThreads workerThreads={workerMinThreads} completionPortThreads={completionPortMinThreads}");
14             Console.WriteLine("************设置最多线程数和最少线程数****************");
15             // 设置最大线程
16             ThreadPool.SetMaxThreads(16, 16);
17             // 设置最小线程
18             ThreadPool.SetMinThreads(8, 8);
19             Console.WriteLine("输出修改后的最多线程数和最少线程数");
20             ThreadPool.GetMaxThreads(out int workerThreads, out int completionPortThreads);
21             Console.WriteLine($"GetMaxThreads workerThreads={workerThreads} completionPortThreads={completionPortThreads}");
22
23             ThreadPool.GetMinThreads(out int workerEditThreads, out int completionPortEditThreads);
24             Console.WriteLine($"GetMinThreads workerThreads={workerEditThreads} completionPortThreads={completionPortEditThreads}");
25             Console.WriteLine($"****************btnThreadPool_Click End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
26 }

结果:

5、ThreadPool实现线程等待

先来看下面一个小例子:

1 ThreadPool.QueueUserWorkItem(p =>
2 {
3       this.DoSomethingLong("btnThreadPool_Click");
4 });
5 Console.WriteLine("等着QueueUserWorkItem完成后才执行");

我们想让异步多线程执行完以后再输出“等着QueueUserWorkItem完成后才执行” 这句话,上面的代码运行效果如下:

从截图中可以看出,效果并不是我们想要的,Thread中提供了暂停、恢复等API,但是ThreadPool中没有这些API,在ThreadPool中要实现线程等待,需要使用到ManualResetEvent类。

ManualResetEvent类的定义如下:

ManualResetEvent需要一个bool类型的参数来表示暂停和停止。上面的代码修改如下:

 1 // 参数为false
 2 ManualResetEvent manualResetEvent = new ManualResetEvent(false);
 3 ThreadPool.QueueUserWorkItem(p =>
 4 {
 5        this.DoSomethingLong("btnThreadPool_Click");
 6        // 设置为true,WaitOne可以通过
 7        manualResetEvent.Set();
 8 });
 9 // 如果为false则过不去,为true可以通过
10 manualResetEvent.WaitOne();
11 Console.WriteLine("等着QueueUserWorkItem完成后才执行");

结果:

ManualResetEvent类的参数值执行顺序如下:

(1)、false--WaitOne等待--Set--true--WaitOne直接过去
(2)、true--WaitOne直接过去--ReSet--false--WaitOne等待

注意:一般情况下,不要阻塞线程池中的线程,因为这样会导致一些无法预见的错误。来看下面的一个例子:

 1 private void btnTrheadPool_Click(object sender, EventArgs e)
 2 {
 3             Console.WriteLine($"****************btnThreadPool_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
 4             // 设置最大线程
 5             ThreadPool.SetMaxThreads(16, 16);
 6             // 设置最小线程
 7             ThreadPool.SetMinThreads(8, 8);
 8             ManualResetEvent manualResetEvent = new ManualResetEvent(false);
 9             for (int i = 0; i < 20; i++)
10             {
11                 int k = i;
12                 ThreadPool.QueueUserWorkItem(p =>
13                 {
14                     Console.WriteLine(k);
15                     if (k < 18)
16                     {
17                         manualResetEvent.WaitOne();
18                     }
19                     else
20                     {
21                         // 设为true
22                         manualResetEvent.Set();
23                     }
24                 });
25             }
26             if (manualResetEvent.WaitOne())
27             {
28                 Console.WriteLine("没有死锁、、、");
29             }
30             Console.WriteLine("等着QueueUserWorkItem完成后才执行");
31             Console.WriteLine($"****************btnThreadPool_Click End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
32 }

启动20个线程,如果k小于18就阻塞当前的线程,结果:

从截图中看出,只执行了16个线程,后面的线程没有执行,这是为什么呢?因为我们在上面设置了线程池中最多可以有16个线程,当16个线程都阻塞的时候,会造成死锁,所以后面的线程不会再执行了。

6、回调

让我们先来了解一下究竟什么是回调。回调就是启动子线程计算,子线程完成委托以后,该线程在去执行后续回调委托。在Thread和ThreadPool中其实是没有回调的,但是我们可以使用委托实现。

先定义委托和回调需要执行的方法:

1 private void DoSomething()
2 {
3     Console.WriteLine($"这是委托执行 当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} ");
4 }
5
6 private void DoSomethingCallback()
7 {
8     Console.WriteLine($"这是回调执行 当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} ");
9 }

定义实现回调的方法:

 1 private void ThreadWithCallback(Action act,Action callback)
 2 {
 3        Thread thread = new Thread(() => {
 4              // 执行委托
 5              act.Invoke();
 6              // 执行回调
 7              callback.Invoke();
 8        });
 9        // 启动线程
10        thread.Start();
11 }

在按钮里面调用该方法:

1 private void btnTrheadPool_Click(object sender, EventArgs e)
2 {
3       Console.WriteLine($"****************btnThreadPool_Click Start 当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
4       Action action = this.DoSomething;
5       Action actCallback = this.DoSomethingCallback;
6       ThreadWithCallback(action, actCallback);
7       Console.WriteLine($"****************btnThreadPool_Click End   当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
8 }

结果:

从截图中可以看出,DoSomethingCallback回调方法是在DoSomething方法执行完毕以后才执行的。

7、线程重用

ThreadPool可以很好的实现线程的重用:

 1 private void btnTrheadPool_Click(object sender, EventArgs e)
 2 {
 3      Console.WriteLine($"****************btnThreadPool_Click Start 当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
 4      // 线程重用
 5      ThreadPool.QueueUserWorkItem(t => this.DoSomethingLong("btnThreadPool_Click"));
 6      ThreadPool.QueueUserWorkItem(t => this.DoSomethingLong("btnThreadPool_Click"));
 7      ThreadPool.QueueUserWorkItem(t => this.DoSomethingLong("btnThreadPool_Click"));
 8      ThreadPool.QueueUserWorkItem(t => this.DoSomethingLong("btnThreadPool_Click"));
 9      ThreadPool.QueueUserWorkItem(t => this.DoSomethingLong("btnThreadPool_Click"));
10      Thread.Sleep(10 * 1000);
11      Console.WriteLine("前面的计算都完成了。。。。。。。。");
12      ThreadPool.QueueUserWorkItem(t => this.DoSomethingLong("btnThreadPool_Click"));
13      ThreadPool.QueueUserWorkItem(t => this.DoSomethingLong("btnThreadPool_Click"));
14      ThreadPool.QueueUserWorkItem(t => this.DoSomethingLong("btnThreadPool_Click"));
15      ThreadPool.QueueUserWorkItem(t => this.DoSomethingLong("btnThreadPool_Click"));
16      ThreadPool.QueueUserWorkItem(t => this.DoSomethingLong("btnThreadPool_Click"));
17      Console.WriteLine($"****************btnThreadPool_Click End   当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
18 }

结果:

可以看出ThreadPool可以实现线程的重用。

Thread不能实现线程的重用:

 1 private void Threads_Click(object sender, EventArgs e)
 2 {
 3      Console.WriteLine($"****************btnThreads_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
 4      for (int i = 0; i < 5; i++)
 5      {
 6          new Thread(() => this.DoSomethingLong("btnThreads_Click")).Start();
 7      }
 8      Thread.Sleep(10 * 1000);
 9      Console.WriteLine("前面的计算都完成了。。。。。。。。");
10      for (int i = 0; i < 5; i++)
11      {
12          new Thread(() => this.DoSomethingLong("btnThreads_Click")).Start();
13      }
14      Console.WriteLine($"****************btnThreads_Click End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
15 }

结果:

可以看出前后两次的线程都是不一样的,所以Thread不能实现线程的重用。

原文地址:https://www.cnblogs.com/dotnet261010/p/9124380.html

时间: 2024-11-06 10:12:22

多线程二:线程池(ThreadPool)的相关文章

细说.NET中的多线程 (二 线程池)

上一章我们了解到,由于线程的创建,销毁都是需要耗费大量资源和时间的,开发者应该非常节约的使用线程资源.最好的办法是使用线程池,线程池能够避免当前进行中大量的线程导致操作系统不停的进行线程切换,当线程数量到达了我们设置的上限,线程会自动排队等待,当线程资源可用时,队列中的线程任务会依次执行,如果没有排队等候的资源,线程会变为闲置状态. 使用ThreadPool来访问线程池 这种做法可以让我们不用那么复杂的去实现创建,重用线程的逻辑,但是也有一些限制,比如由他内置的方法,我们不知道什么时候线程池里面

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

在多线程的程序中,经常会出现两种情况: 一种情况:   应用程序中,线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应                   这一般使用ThreadPool(线程池)来解决: 另一种情况:线程平时都处于休眠状态,只是周期性地被唤醒                   这一般使用Timer(定时器)来解决: 本篇文章单单讲线程池[ThreadPool] ThreadPool类 MSDN帮助信息: http://msdn.microsoft.com/z

多线程系列(2)线程池ThreadPool

上一篇文章我们总结了多线程最基础的知识点Thread,我们知道了如何开启一个新的异步线程去做一些事情.可是当我们要开启很多线程的时候,如果仍然使用Thread我们需要去管理每一个线程的启动,挂起和终止,显然是很麻烦的一件事情.还好.net framework为我们提供了线程池ThreadPool来帮助我们来管理这些线程,这样我们就不再需要手动地去终止这些线程.这一篇文章就让我们来学习一下线程池ThreadPool吧.关于它我想从以下几个方面进行总结. 认识线程池ThreadPool Thread

C#多线程学习 之 线程池[ThreadPool]

在多线程的程序中,经常会出现两种情况: 一种情况:   应用程序中,线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应                   这一般使用ThreadPool(线程池)来解决: 另一种情况:线程平时都处于休眠状态,只是周期性地被唤醒                   这一般使用Timer(定时器)来解决: 本篇文章单单讲线程池[ThreadPool] ThreadPool类 MSDN帮助信息: http://msdn.microsoft.com/z

多线程Thread,线程池ThreadPool

首先我们先增加一个公用方法DoSomethingLong(string name),这个方法下面的举例中都有可能用到 1 #region Private Method 2 /// <summary> 3 /// 一个比较耗时耗资源的私有方法 4 /// </summary> 5 /// <param name="name"></param> 6 private void DoSomethingLong(string name) 7 { 8

高效线程池(threadpool)的实现

高效线程池(threadpool)的实现 Nodejs编程是全异步的,这就意味着我们不必每次都阻塞等待该次操作的结果,而事件完成(就绪)时会主动回调通知我们.在网络编程中,一般都是基于Reactor线程模型的变种,无论其怎么演化,其核心组件都包含了Reactor实例(提供事件注册.注销.通知功能).多路复用器(由操作系统提供,比如kqueue.select.epoll等).事件处理器(负责事件的处理)以及事件源(linux中这就是描述符)这四个组件.一般,会单独启动一个线程运行Reactor实例

C#多线程之线程池篇3

在上一篇C#多线程之线程池篇2中,我们主要学习了线程池和并行度以及如何实现取消选项的相关知识.在这一篇中,我们主要学习如何使用等待句柄和超时.使用计时器和使用BackgroundWorker组件的相关知识. 五.使用等待句柄和超时 在这一小节中,我们将学习如何在线程池中实现超时和正确地实现等待.具体操作步骤如下: 1.使用Visual Studio 2015创建一个新的控制台应用程序. 2.双击打开"Program.cs"文件,编写代码如下所示: 1 using System; 2 u

多线程及线程池学习心得

一.线程的应用与特点 多线程是程序员不可或缺的技术能力,多线程技术在各个方面都有应用,特别在性能优化上更是起到至关重要的作用.但是,如果多线程写得不好,往往会适得其反,特别是高并发时会造成阻塞.超时等现象.多线程具有以下特点:1.独立性,拥有自己独立的资源,拥有自己私有的地址空间:2.动态性,进程具有自己的生命周期和各种不同的状态:3.并发性,多个进程可以在单个处理器上并发执行,不会相互影响,并行是指同一时刻有多条指令在多个处理器上同时执行.线程是进程的组成部分,一个进程可以拥有多个线程,一个线

C#多线程之线程池篇2

在上一篇C#多线程之线程池篇1中,我们主要学习了如何在线程池中调用委托以及如何在线程池中执行异步操作,在这篇中,我们将学习线程池和并行度.实现取消选项的相关知识. 三.线程池和并行度 在这一小节中,我们将学习对于大量的异步操作,使用线程池和分别使用单独的线程在性能上有什么差异性.具体操作步骤如下: 1.使用Visual Studio 2015创建一个新的控制台应用程序. 2.双击打开"Program.cs"文件,编写代码如下所示: 1 using System; 2 using Sys