C# 线程知识--使用ThreadPool执行异步操作

C# 线程知识--使用ThreadPool执行异步操作

在应用程序中有许多复杂的任务,对于这些任务可能需要使用一个或多个工作线程或I/O线程来协作处理,比如:定时任务、数据库数据操作、web服务、文件的处理等。这些任务可能会非常耗费时间,为了是用户界面能及时响应,就会启用一个其他线程来并行处理任务。线程的创建和销毁操作是非常昂贵的,过多的线程会带来内存资源的消耗以及操作系统调度可执行线程并执行上下文切换导致的时间消耗,所以过多线程会损坏应用程序的性能。如果创建过的线程能反复使用就能解决上面的一些问题,因此,CLR使用了线程池来管理线程。

1. 线程池基础

每个CLR 拥有一个线程池,这个线程池由CLR控制的APPDomain 共享。在线程池内部,它自己维护着一个操作请求队列,应用程序需要执行某个任务时,就需要调用线程池的一个方法(通常是QueueUserWorkItem 方法)将任务添加到线程池工作项中,线程池就会将任务分派给一个线程池线程处理,如果线程池中没有线程,就会创建一个线程来处理这个任务。当任务执行完成以后,这个线程会回到线程池中处于空闲状态,等待下一个执行任务。由于线程不会销毁,所以使用线程池线程在执行任务的速度上会更快。

如果线程池中的任务过多超过了现有线程的处理能力时,线程池就会根据需要在创建更多的线程。由于每个线程都要占用一定的内存资源,所以当线程池空闲线程(长时间不执行任务的线程)过多时,线程池中线程会自动醒来销毁多余的空闲线程,以减少资源的使用。

在线程池内部,所有线程都是后台线程并且调度优先级都为普通(ThreadPriority.Normal),这些线程分为工作者或I/O线程,当线程池线程执行的任务是一个复杂的计算任务时,使用的就是工作者线程。如果执行的任务与I/O相关,就会使用I/O线程。

2. 使用ThreadPool 类执行异步任务

ThreadPool 类是一个静态类型类,使用ThreadPool 类执行异步时通常调用ThreadPool 的 QueueUserWorkItem 方法,这个方法有一个重载版本,如下:

public static bool QueueUserWorkItem(WaitCallback callBack);

public static bool QueueUserWorkItem(WaitCallback callBack, object state);

QueueUserWorkItem 方法接受一个WaitCallback 类型的委托作为回调方法以及可以选择传递一个线程池线程执行回调方法时所需要的数据对象。

WaitCallback 委托类型的定义如下:

public delegate void WaitCallback(object state);

线程池的QueueUserWorkItem方法在调用以后会立即返回,所传递的回调方法会有以后线程池线程执行。使用线程池线程执行异步任务代码如下:

   1:    static void Main(string[] args)
   2:          {
   3:              Console.WriteLine("主线程开始执行任务。线程ID:{0}",Thread.CurrentThread.ManagedThreadId);
   4:   
   5:              //使用 ThreadPool.QueueUserWorkItem 方法将一个异步任务添加到线程池任务队列中,
   6:              //可以为线程池线程执行方法时传递一个数据对象,
   7:              //如果不需要传递数据可以使用QueueUserWorkItem只有WaitCallback一个参数类型的版本,
   8:              //或传递null
   9:              ThreadPool.QueueUserWorkItem(state => {
  10:                  Console.WriteLine("线程池线程开始执行异步任务。线程ID:{0}",Thread.CurrentThread.ManagedThreadId);
  11:                  for (int i = 0; i < 10; i++)
  12:                  {
  13:                      Console.WriteLine(i);
  14:                  }
  15:               
  16:              },null);
  17:   
  18:              Console.WriteLine("主线程执行其他任务。线程ID:{0}", Thread.CurrentThread.ManagedThreadId);
  19:              //使调用线程睡眠2000毫秒,等待线程池线程执行完成。
  20:              Thread.Sleep(2000);
  21:   
  22:              Console.WriteLine("主线程继续执行任务。线程ID:{0}", Thread.CurrentThread.ManagedThreadId);
  23:          }

程序运行结果如图:

3.线程池对线程的管理

CLR 允许开发人员设置线程池需要创建的最大和最小工作者线程和I/O线程,但是最好不要设置线程池的线程数,CLR对线程池的工作者线程和I/O线程的最大线程数设置了1000的默认值。我们可以使用线程池的如下方法对线程池线程数进行设置和获取以及获得最大线程池线程数和当前运行线程数之间的差值:

//设置可以同时处于活动状态的线程池的请求数目。
// 所有大于此数目的请求将保持排队状态,直到线程池线程变为可用。
public static bool SetMaxThreads(int workerThreads, int completionPortThreads);
 // 发出新的请求时,在切换到管理线程创建和销毁的算法之前设置线程池按需创建的线程的最小数量。
public static bool SetMinThreads(int workerThreads, int completionPortThreads);
 //检索可以同时处于活动状态的线程池请求的数目。 所有大于此数目的请求将保持排队状态,直到线程池线程变为可用。
 public static void GetMaxThreads(out int workerThreads, out int completionPortThreads);
//发出新的请求时,在切换到管理线程创建和销毁的算法之前检索线程池按需创建的线程的最小数量。
 public static void GetMinThreads(out int workerThreads, out int completionPortThreads);
 //获得最大线程池线程数和当前运行线程数之间的差值。
public static void GetAvailableThreads(out int workerThreads, out int completionPortThreads);
 
示例代码:
   1:    static void SetThreadNumber() {
   2:              int worker, io;
   3:              //获得线程池默认最大线程数
   4:              ThreadPool.GetMaxThreads(out worker,out io);
   5:              Console.WriteLine("1、CLR线程池默认最大线程数据,工作者线程数:{0},IO线程数:{1}",worker,io);
   6:              //设置线程池最大线程数
   7:              ThreadPool.SetMaxThreads(100,100);
   8:              ThreadPool.QueueUserWorkItem(state =>
   9:              {
  10:                  Thread.Sleep(2000);
  11:                  Console.WriteLine("4、线程池线程开始执行异步任务。线程ID:{0}", Thread.CurrentThread.ManagedThreadId);
  12:              
  13:              });
  14:              Console.WriteLine("2、自定义设置线程池默认最大线程数据后,工作者线程数:{0},IO线程数:{1}", worker, io);
  15:              //获得最大线程池线程数和当前运行线程数之间的差值。
  16:              ThreadPool.GetAvailableThreads(out worker,out io);
  17:              Console.WriteLine("3、获得最大线程池线程数和当前运行线程数之间的差值,工作者线程:{0},IO线程:{1}",worker,io);
  18:              Console.Read();
  19:          }

执行结果:

线程池的工作者线程是许多异步计算任务所使用的线程,在线程池内部,工作者线程采用先入先出的算法将工作项从线程的全局队列中取出工作项并执行任务。在同一时刻可能有多个工作者线程从全局队列中取出工作项,因此所有工作者线程都会竞争一个线程同步锁,以保证两个或更多工作者线程不会在同一时刻取出工作项。线程池工作者线程数据结构如下图:

线程工作者线程数据结构图

线程池在创建工作者线程时,默认会创建ThreadPool.SetMinThreads 方法锁设置的值。如果没有设置这个值,就会创建与应用程序进程允许的使用的CPU数相同的工作者线程,这些工作者线程监视线程池任务的执行情况,得以动态的创建更多或销毁空闲线程。

4.线程执行上下文的流动

每个线程都有一个执行上下文,执行上下文包涵了安全设置、宿主设置和逻辑调用上下文数据,CLR默认是把初始线程的执行上下文数据向辅助线程流动,初始线程在收集和复制执行上下文数据并传递到辅助线程时会带来性能的损失。如果不需要这些执行上下文数据可以使用 System.Threading.ExecutionContext 类阻止执行上下文数据的流转。常用方法如下:

        //     指示当前是否取消了执行上下文的流动。
        public static bool IsFlowSuppressed();
        //     恢复执行上下文在异步线程之间的流动。
        public static void RestoreFlow();
       //取消执行上下文在异步线程之间的流动。
        public static AsyncFlowControl SuppressFlow();
 
执行上下文流转控制示例代码:
   1:   static void ExecContext() {
   2:              //把对象存储在调用逻辑上下文中
   3:              System.Runtime.Remoting.Messaging.CallContext.LogicalSetData("Key","这是主线程的执行上下文数据");
   4:              Console.WriteLine("主线程执行上下文数据:{0}",System.Runtime.Remoting.Messaging.CallContext.LogicalGetData("Key"));
   5:              //阻止主线程执行上下文数据的传递
   6:              ExecutionContext.SuppressFlow();
   7:              Console.WriteLine("调用SuppressFlow方法以后上下文传递流转是否被阻止:{0}", ExecutionContext.IsFlowSuppressed().ToString());
   8:   
   9:              ThreadPool.QueueUserWorkItem(state => {
  10:                  Console.WriteLine("执行上下文数据被阻止传递:{0}", 
  11:                      System.Runtime.Remoting.Messaging.CallContext.LogicalGetData("Key"));
  12:              });
  13:             
  14:              //恢复主线程执行上下文数据的传递
  15:              ExecutionContext.RestoreFlow();
  16:              Console.WriteLine("调用RestoreFlow方法以后上下文传递流转是否被阻止:{0}", ExecutionContext.IsFlowSuppressed().ToString());
  17:   
  18:              ThreadPool.QueueUserWorkItem(state =>
  19:              {
  20:                  Console.WriteLine("执行上下文数据没有被阻止传递:{0}",
  21:                      System.Runtime.Remoting.Messaging.CallContext.LogicalGetData("Key"));
  22:              });
  23:              Console.Read();
  24:          }

执行结果:

本文简单介绍了CLR 中线程池的基础和使用以及线程池对线程的管理,如果需要构建可伸缩性、高性能、高并发的应用程序,线程池是一种很好的进行这些处理的技术。

时间: 2024-11-04 19:14:05

C# 线程知识--使用ThreadPool执行异步操作的相关文章

[转]C# 线程知识--使用Task执行异步操作

C# 线程知识--使用Task执行异步操作 ??????在C#4.0之前需要执行一个复杂的异步操作时,只能使用CLR线程池技术来执行一个任务.线程池执行异步任务时,不知道任务何时完成,以及任务的在任务完成后不能获取到返回值.但是在C#4.0中引人了一个的任务(System.Threading.Tasks命名空间的类型)机制来解决异步操作完成时间和完成后返回值的问题. 1.使用Task类创建并执行简单任务 ??? 通过使用Task的构造函数来创建任务,并调用Start方法来启动任务并执行异步操作.

C# 线程知识--使用Task执行异步操作

来源:https://www.cnblogs.com/pengstone/archive/2012/12/23/2830238.html 在C#4.0之前需要执行一个复杂的异步操作时,只能使用CLR线程池技术来执行一个任务.线程池执行异步任务时,不知道任务何时完成,以及任务的在任务完成后不能获取到返回值.但是在C#4.0中引人了一个的任务(System.Threading.Tasks命名空间的类型)机制来解决异步操作完成时间和完成后返回值的问题. 1.使用Task类创建并执行简单任务 通过使用T

ThreadPool执行异步操作

使用ThreadPool_类执行异步任务 /* ThreadPool 类是一个静态类型类,使用ThreadPool 类执行异步时通常调用ThreadPool 的 QueueUserWorkItem 方法,这个方法有一个重载版本,如下: public static bool QueueUserWorkItem(WaitCallback callBack); public static bool QueueUserWorkItem(WaitCallback callBack, object stat

线程池(ThreadPool)

线程池(ThreadPool) https://www.cnblogs.com/jonins/p/9369927.html 线程池概述 由系统维护的容纳线程的容器,由CLR控制的所有AppDomain共享.线程池可用于执行任务.发送工作项.处理异步 I/O.代表其他线程等待以及处理计时器. 线程池与线程 性能:每开启一个新的线程都要消耗内存空间及资源(默认情况下大约1 MB的内存),同时多线程情况下操作系统必须调度可运行的线程并执行上下文切换,所以太多的线程还对性能不利.而线程池其目的是为了减少

C#执行异步操作的几种方式比较和总结

原文出处: Durow(@Durow)   欢迎分享原创到伯乐头条 0×00 引言 之前写程序的时候在遇到一些比较花时间的操作例如HTTP请求时,总是会new一个Thread处理.对XxxxxAsync()之类的方法也没去了解过,倒也没遇到什么大问题.最近因为需求要求用DevExpress写界面,跑起来后发现比Native控件效率差好多.这才想到之前看到的“金科玉律”:不要在UI线程上执行界面无关的操作,因此集中看了下C#的异步操作,分享一下自己的比较和总结. 0×01 测试方法 IDE:VS2

线程池之ThreadPool类与辅助线程 - &lt;第二篇&gt;

一.CLR线程池 管理线程开销最好的方式: 尽量少的创建线程并且能将线程反复利用(线程池初始化时没有线程,有程序请求线程则创建线程): 最好不要销毁而是挂起线程达到避免性能损失(线程池创建的线程完成任务后以挂起状态回到线程池中,等待下次请求): 通过一个技术达到让应用程序一个个执行工作,类似于一个队列(多个应用程序请求线程池,线程池会将各个应用程序排队处理): 如果某一线程长时间挂起而不工作的话,需要彻底销毁并且释放资源(线程池自动监控长时间不工作的线程,自动销毁): 如果线程不够用的话能够创建

C#多线程--线程池(ThreadPool)

先引入一下线程池的概念: 百度百科:线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线程池线程都是后台线程.每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中.如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙.如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值.超过最大值的线程可以排队,但他们要等到其他线程

线程(Thread)、线程池(ThreadPool)技术

线程:是Windows任务调度的最小单位.线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针.程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数,在一个应用程序中,常常需要使用多个线程来处理不同的事情,这样可以提高程序的运行效率,也不会使主界面出现无响应的情况.在这里主要介绍线程(Thread).线程池(ThreadPool)两种不同创建线程的区别 在通常的情况下,当我们需要开启一个新的线程时,我们直接通过Thread(继承自 System.Threading;)去创建

Java线程知识拾遗

知识回顾 进程与线程是常常被提到的两个概念.进程拥有独立的代码段.数据空间,线程共享代码段和数据空间,但有独立的栈空间.线程是操作系统调度的最小单位,通常一个进程会包含一个或多个线程.多线程和多进程都可以实现并发处理,如 nginx 使用多进程方式.tomcat 使用多线程方式.Apache 支持混合使用.在 C/C++ 等语言中可以同时使用多进程和多线程,而在 Java 中只能使用多线程. 在 Java 中,创建线程的唯一方式是创建 Thread 类的实例,调用实例的 start() 方法启动