[.Net线程处理系列]专题二:线程池中的工作者线程-成都快上网建站

[.Net线程处理系列]专题二:线程池中的工作者线程

目录:

创新互联是专业的藁城网站建设公司,藁城接单;提供成都网站制作、网站设计、外贸网站建设,网页设计,网站设计,建网站,PHP网站建设等专业做网站服务;采用PHP框架,可快速的进行藁城网站开发网页制作和功能扩展;专业做搜索引擎喜爱的网站,专业的做网站团队,希望更多企业前来合作!

一、上节补充

二、CLR线程池基础

三、通过线程池的工作者线程实现异步

四、使用委托实现异步

五、任务

六、小结

 

一、上节补充

对于Thread类还有几个常用方法需要说明的。

1.1 Suspend和Resume方法

    这两个方法在.net Framework 1.0 的时候就支持的方法,他们分别可以挂起线程和恢复挂起的线程。但在.net Framework 2.0以后的版本中这两个方法都过时了,MSDN的解释是这样:

警告:

不要使用 Suspend 和 Resume 方法来同步线程的活动。您无法知道挂起线程时它正在执行什么代码。如果您在安全权限评估期间挂起持有锁的线程,则 AppDomain中的其他线程可能被阻止。如果您在线程正在执行类构造函数时挂起它,则 AppDomain中尝试使用该类的其他线程将被阻止。这样很容易发生死锁。

对于这个解释可能有点抽象吧,让我们来看看一段代码可能会清晰点:

  1. class Program  
  2.     {  
  3.         static void Main(string[] args)  
  4.         {  
  5.             // 创建一个线程来测试 
  6.             Thread thread1 = new Thread(TestMethod);        
  7.             thread1.Name = "Thread1";     
  8.             thread1.Start();      
  9.             Thread.Sleep(2000);  
  10.             Console.WriteLine("Main Thread is running");  
  11.             ////int b = 0; 
  12.             ////int a = 3 / b; 
  13.             ////Console.WriteLine(a); 
  14.             thread1.Resume();       
  15.             Console.Read();  
  16.         }  
  17.  
  18.         private static void TestMethod()  
  19.         {       
  20.             Console.WriteLine("Thread: {0} has been suspended!", Thread.CurrentThread.Name);  
  21.         
  22.             //将当前线程挂起 
  23.             Thread.CurrentThread.Suspend();            
  24.             Console.WriteLine("Thread: {0} has been resumed!", Thread.CurrentThread.Name);  
  25.         }  
  26.     } 
 
     在上面这段代码中thread1线程是在主线程中恢复的,但当主线程发生异常时,这时候就thread1一直处于挂起状态,此时thread1所使用的资源就不能释放(除非强制终止进程),当另外线程需要使用这快资源的时候, 这时候就很可能发生死锁现象。

上面一段代码还存在一个隐患,请看下面一小段代码:

  1. class Program  
  2.     {  
  3.         static void Main(string[] args)  
  4.         {  
  5.             // 创建一个线程来测试 
  6.             Thread thread1 = new Thread(TestMethod);        
  7.             thread1.Name = "Thread1";     
  8.             thread1.Start();  
  9.             Console.WriteLine("Main Thread is running");  
  10.             thread1.Resume();       
  11.             Console.Read();  
  12.         }  
  13.  
  14.         private static void TestMethod()  
  15.         {       
  16.             Console.WriteLine("Thread: {0} has been suspended!", Thread.CurrentThread.Name);  
  17.             Thread.Sleep(1000);  
  18.  
  19.             //将当前线程挂起 
  20.             Thread.CurrentThread.Suspend();            
  21.             Console.WriteLine("Thread: {0} has been resumed!", Thread.CurrentThread.Name);  
  22.         }  
  23.     } 

    当主线程跑(运行)的太快,做完自己的事情去唤醒thread1时,此时thread1还没有挂起而起唤醒thread1,此时就会出现异常了。并且上面使用的Suspend和Resume方法,编译器已经出现警告了,提示这两个方法已经过时, 所以在我们平时使用中应该尽量避免。

1.2 Abort和 Interrupt方法

Abort方法和Interrupt都是用来终止线程的,但是两者还是有区别的。

1、他们抛出的异常不一样,Abort 方法抛出的异常是ThreadAbortException, Interrupt抛出的异常为ThreadInterruptedException

2、调用interrupt方法的线程之后可以被唤醒,然而调用Abort方法的线程就直接被终止不能被唤醒的。

下面一段代码是演示Abort方法的使用

  1. using System;  
  2. using System.Threading;  
  3.  
  4. namespace ConsoleApplication1  
  5. {  
  6.     class Program  
  7.     {  
  8.         static void Main(string[] args)  
  9.         {  
  10.             Thread abortThread = new Thread(AbortMethod);  
  11.             abortThread.Name = "Abort Thread";  
  12.             abortThread.Start();  
  13.             Thread.Sleep(1000);  
  14.             try 
  15.             {  
  16.                 abortThread.Abort();       
  17.             }  
  18.             catch   
  19.             {  
  20.                 Console.WriteLine("{0} Exception happen in Main Thread", Thread.CurrentThread.Name);  
  21.                 Console.WriteLine("{0} Status is:{1} In Main Thread ", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState);  
  22.             }  
  23.             finally 
  24.             {  
  25.                 Console.WriteLine("{0} Status is:{1} In Main Thread ", abortThread.Name, abortThread.ThreadState);  
  26.             }  
  27.  
  28.             abortThread.Join();  
  29.             Console.WriteLine("{0} Status is:{1} ", abortThread.Name, abortThread.ThreadState);  
  30.             Console.Read();  
  31.              
  32.         }  
  33.  
  34.         private static void AbortMethod()  
  35.         {  
  36.             try 
  37.             {  
  38.                 Thread.Sleep(5000);  
  39.             }  
  40.             catch(Exception e)  
  41.             {  
  42.                 Console.WriteLine(e.GetType().Name);  
  43.                 Console.WriteLine("{0} Exception happen In Abort Thread", Thread.CurrentThread.Name);  
  44.                 Console.WriteLine("{0} Status is:{1} In Abort Thread ", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState);  
  45.             }  
  46.             finally 
  47.             {  
  48.                 Console.WriteLine("{0} Status is:{1} In Abort Thread", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState);  
  49.             }  
  50.         }  
  51.     } 
运行结果:[.Net线程处理系列]专题二:线程池中的工作者线程

    从运行结果可以看出,调用Abort方法的线程引发的异常类型为ThreadAbortException, 以及异常只会在 调用Abort方法的线程中发生,而不会在主线程中抛出,并且调用Abort方法后线程的状态不是立即改变为Aborted状态,而是从AbortRequested->Aborted。

Interrupt方法:

  1. using System;  
  2. using System.Threading;  
  3.  
  4. namespace ConsoleApplication1  
  5. {  
  6.     class Program  
  7.     {  
  8.         static void Main(string[] args)  
  9.         { Thread interruptThread = new Thread(AbortMethod);  
  10.             interruptThread.Name = "Interrupt Thread";  
  11.             interruptThread.Start();    
  12.             interruptThread.Interrupt();       
  13.              
  14.             interruptThread.Join();  
  15.             Console.WriteLine("{0} Status is:{1} ", interruptThread.Name, interruptThread.ThreadState);  
  16.             Console.Read();       
  17.         }  
  18.  
  19.         private static void AbortMethod()  
  20.         {  
  21.             try 
  22.             {  
  23.                 Thread.Sleep(5000);  
  24.             }  
  25.             catch(Exception e)  
  26.             {  
  27.                 Console.WriteLine(e.GetType().Name);  
  28.                 Console.WriteLine("{0} Exception happen In Interrupt Thread", Thread.CurrentThread.Name);  
  29.                 Console.WriteLine("{0} Status is:{1} In Interrupt Thread ", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState);  
  30.             }  
  31.             finally 
  32.             {  
  33.                 Console.WriteLine("{0} Status is:{1} In Interrupt Thread", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState);  
  34.             }  
  35.         }  
  36.     }  
 运行结果:[.Net线程处理系列]专题二:线程池中的工作者线程

    从结果中可以得到,调用Interrupt方法抛出的异常为:ThreadInterruptException, 以及当调用Interrupt方法后线程的状态应该是中断的, 但是从运行结果看此时的线程因为了Join,Sleep方法而唤醒了线程,为了进一步解释调用Interrupt方法的线程可以被唤醒, 我们可以在线程执行的方法中运用循环,如果线程可以唤醒,则输出结果中就一定会有循环的部分,然而调用Abort方法线程就直接终止,就不会有循环的部分,下面代码相信大家看后肯定会更加理解两个方法的区别的:

  1. using System;  
  2. using System.Threading;  
  3.  
  4. namespace ConsoleApplication2  
  5. {  
  6.     class Program  
  7.     {  
  8.         static void Main(string[] args)  
  9.         {  
  10.             Thread thread1 = new Thread(TestMethod);  
  11.             thread1.Start();  
  12.             Thread.Sleep(100);  
  13.  
  14.             thread1.Interrupt();  
  15.             Thread.Sleep(3000);  
  16.             Console.WriteLine("after finnally block, the Thread1 status is:{0}", thread1.ThreadState);  
  17.             Console.Read();  
  18.         }  
  19.         private static void TestMethod()  
  20.         {  
  21.               
  22.             for (int i = 0; i < 4; i++)  
  23.             {  
  24.                 try 
  25.                 {  
  26.                     Thread.Sleep(2000);  
  27.                     Console.WriteLine("Thread is Running");  
  28.                 }  
  29.                 catch (Exception e)  
  30.                 {  
  31.                     if (e != null)  
  32.                     {  
  33.                         Console.WriteLine("Exception {0} throw ", e.GetType().Name);  
  34.                     }  
  35.                 }  
  36.                 finally 
  37.                 {  
  38.                     Console.WriteLine("Current Thread status is:{0} ", Thread.CurrentThread.ThreadState);  
  39.                 }  
  40.             }  
  41.         }  
  42.     }  
运行结果为:[.Net线程处理系列]专题二:线程池中的工作者线程

如果把上面的 thread1.Interrupt();改为 thread1.Abort(); 运行结果为:[.Net线程处理系列]专题二:线程池中的工作者线程

 二、线程池基础

    首先,创建和销毁线程是一个要耗费大量时间的过程,另外,太多的线程也会浪费内存资源,所以通过Thread类来创建过多的线程反而有损于性能,为了改善这样的问题 ,.net中就引入了线程池。

    线程池形象的表示就是存放应用程序中使用的线程的一个集合(就是放线程的地方,这样线程都放在一个地方就好管理了)。CLR初始化时,线程池中是没有线程的,在内部, 线程池维护了一个操作请求队列,当应用程序想执行一个异步操作时,就调用一个方法,就将一个任务放到线程池的队列中,线程池中代码从队列中提取任务,将这个任务委派给一个线程池线程去执行,当线程池线程完成任务时,线程不会被销毁,而是返回到线程池中,等待响应另一个请求。由于线程不被销毁, 这样就可以避免因为创建线程所产生的性能损失。

注意:通过线程池创建的线程默认为后台线程,优先级默认为Normal.

三、通过线程池的工作者线程实现异步

3.1 创建工作者线程的方法

public static bool QueueUserWorkItem (WaitCallback callBack);

public static bool QueueUserWorkItem(WaitCallback callback, Object state);

这两个方法向线程池的队列添加一个工作项(work item)以及一个可选的状态数据。然后,这两个方法就会立即返回。

工作项其实就是由callback参数标识的一个方法,该方法将由线程池线程执行。同时写的回调方法必须匹配System.Threading.WaitCallback委托类型,定义为:

public delegate void WaitCallback(Object state);

下面演示如何通过线程池线程来实现异步调用:

  1. using System;  
  2. using System.Threading;  
  3.  
  4. namespace ThreadPoolUse  
  5. {  
  6.     class Program  
  7.     {  
  8.         static void Main(string[] args)  
  9.         {  
  10.             // 设置线程池中处于活动的线程的最大数目 
  11.             // 设置线程池中工作者线程数量为1000,I/O线程数量为1000 
  12.             ThreadPool.SetMaxThreads(1000, 1000);  
  13.             Console.WriteLine("Main Thread: queue an asynchronous method");  
  14.             PrintMessage("Main Thread Start");  
  15.  
  16.             // 把工作项添加到队列中,此时线程池会用工作者线程去执行回调方法 
  17.             ThreadPool.QueueUserWorkItem(asyncMethod);  
  18.             Console.Read();  
  19.         }  
  20.  
  21.         // 方法必须匹配WaitCallback委托 
  22.         private static void asyncMethod(object state)  
  23.         {  
  24.             Thread.Sleep(1000);  
  25.             PrintMessage("Asynchoronous Method");  
  26.             Console.WriteLine("Asynchoronous thread has worked ");  
  27.         }  
  28.  
  29.         // 打印线程池信息 
  30.         private static void PrintMessage(String data)  
  31.         {  
  32.             int workthreadnumber;  
  33.             int iothreadnumber;  
  34.  
  35.             // 获得线程池中可用的线程,把获得的可用工作者线程数量赋给workthreadnumber变量 
  36.             // 获得的可用I/O线程数量给iothreadnumber变量 
  37.             ThreadPool.GetAvailableThreads(out workthreadnumber, out iothreadnumber);  
  38.  
  39.             Console.WriteLine("{0}\n CurrentThreadId is {1}\n CurrentThread is background :{2}\n WorkerThreadNumber is:{3}\n IOThreadNumbers is: {4}\n",  
  40.                 data,  
  41.                 Thread.CurrentThread.ManagedThreadId,   
  42.                 Thread.CurrentThread.IsBackground.ToString(),  
  43.                 workthreadnumber.ToString(),  
  44.                 iothreadnumber.ToString());  
  45.         }  
  46.     }  
运行结果:  [.Net线程处理系列]专题二:线程池中的工作者线程

从结果中可以看出,线程池中的可用的工作者线程少了一个,用去执行回调方法了。

ThreadPool.QueueUserWorkItem(WaitCallback callback,Object state) 方法可以把object对象作为参数传送到回调函数中,使用和ThreadPool.QueueUserWorkItem(WaitCallback callback)的使用和类似,这里就不列出了。

3.2协作式取消

    .net Framework提供了取消操作的模式, 这个模式是协作式的。为了取消一个操作,首先必须创建一个System.Threading.CancellationTokenSource对象。

下面代码演示了协作式取消的使用,主要实现当用户在控制台敲下回车键后就停止数数方法。

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.Threading;  
  6.  
  7. namespace ConsoleApplication3  
  8. {  
  9.     class Program  
  10.     {  
  11.         static void Main(string[] args)  
  12.         {  
  13.             ThreadPool.SetMaxThreads(1000, 1000);  
  14.             Console.WriteLine("Main thread run");      
  15.             PrintMessage("Start");  
  16.             Run();  
  17.             Console.ReadKey();  
  18.         }  
  19.  
  20.         private static void Run()  
  21.         {  
  22.             CancellationTokenSource cts = new CancellationTokenSource();  
  23.  
  24.             // 这里用Lambda表达式的方式和使用委托的效果一样的,只是用了Lambda后可以少定义一个方法。 
  25.             // 这在这里就是让大家明白怎么lambda表达式如何由委托转变的 
  26.             ////ThreadPool.QueueUserWorkItem(o => Count(cts.Token, 1000)); 
  27.  
  28.             ThreadPool.QueueUserWorkItem(callback, cts.Token);  
  29.  
  30.             Console.WriteLine("Press Enter key to cancel the operation\n");  
  31.             Console.ReadLine();  
  32.  
  33.             // 传达取消请求 
  34.             cts.Cancel();  
  35.         }  
  36.           
  37.         private static void callback(object state)  
  38.         {  
  39.             Thread.Sleep(1000);  
  40.             PrintMessage("Asynchoronous Method Start");  
  41.             CancellationToken token =(CancellationToken)state;      
  42.             Count(token, 1000);  
  43.         }  
  44.  
  45.         // 执行的操作,当受到取消请求时停止数数 
  46.         private static void Count(CancellationToken token,int countto)  
  47.         {  
  48.             for (int i = 0; i < countto; i++)  
  49.             {  
  50.                 if (token.IsCancellationRequested)  
  51.                 {  
  52.                     Console.WriteLine("Count is canceled");  
  53.                     break;  
  54.                 }  
  55.  
  56.                 Console.WriteLine(i);  
  57.                 Thread.Sleep(300);  
  58.             }  
  59.               
  60.             Console.WriteLine("Cout has done");         
  61.         }  
  62.  
  63.         // 打印线程池信息 
  64.         private static void PrintMessage(String data)  
  65.         {  
  66.             int workthreadnumber;  
  67.             int iothreadnumber;  
  68.  
  69.             // 获得线程池中可用的线程,把获得的可用工作者线程数量赋给workthreadnumber变量 
  70.             // 获得的可用I/O线程数量给iothreadnumber变量 
  71.             ThreadPool.GetAvailableThreads(out workthreadnumber, out iothreadnumber);  
  72.  
  73.             Console.WriteLine("{0}\n CurrentThreadId is {1}\n CurrentThread is background :{2}\n WorkerThreadNumber is:{3}\n IOThreadNumbers is: {4}\n",  
  74.                 data,  
  75.                 Thread.CurrentThread.ManagedThreadId,  
  76.                 Thread.CurrentThread.IsBackground.ToString(),  
  77.                 workthreadnumber.ToString(),  
  78.                 iothreadnumber.ToString());  
  79.         }  
  80.     }  
运行结果:[.Net线程处理系列]专题二:线程池中的工作者线程

 四、使用委托实现异步

    通过调用ThreadPool的QueueUserWorkItem方法来来启动工作者线程非常方便,但委托WaitCallback指向的是带有一个参数的无返回值的方法,如果我们实际操作中需要有返回值,或者需要带有多个参数, 这时通过这样的方式就难以实现, 为了解决这样的问题,我们可以通过委托来建立工作这线程,

下面代码演示了使用委托如何实现异步:

  1. using System;  
  2. using System.Threading;  
  3.  
  4. namespace Delegate  
  5. {  
  6.     class Program  
  7.     {  
  8.         // 使用委托的实现的方式是使用了异步变成模型APM(Asynchronous Programming Model) 
  9.         // 自定义委托 
  10.         private delegate string MyTestdelegate();  
  11.  
  12.         static void Main(string[] args)  
  13.         {  
  14.             ThreadPool.SetMaxThreads(1000, 1000);  
  15.             PrintMessage("Main Thread Start");  
  16.  
  17.             //实例化委托 
  18.             MyTestdelegate testdelegate = new MyTestdelegate(asyncMethod);  
  19.  
  20.             // 异步调用委托 
  21.             IAsyncResult result = testdelegate.BeginInvoke(null, null);  
  22.  
  23.             // 获取结果并打印出来 
  24.             string returndata = testdelegate.EndInvoke(result);  
  25.             Console.WriteLine(returndata);  
  26.  
  27.             Console.ReadLine();  
  28.         }  
  29.         private static string asyncMethod()  
  30.         {  
  31.             Thread.Sleep(1000);  
  32.             PrintMessage("Asynchoronous Method");  
  33.             return "Method has completed";  
  34.         }  
  35.  
  36.         // 打印线程池信息 
  37.         private static void PrintMessage(String data)  
  38.         {  
  39.             int workthreadnumber;  
  40.             int iothreadnumber;  
  41.  
  42.             // 获得线程池中可用的线程,把获得的可用工作者线程数量赋给workthreadnumber变量 
  43.             // 获得的可用I/O线程数量给iothreadnumber变量 
  44.             ThreadPool.GetAvailableThreads(out workthreadnumber, out iothreadnumber);  
  45.  
  46.             Console.WriteLine("{0}\n CurrentThreadId is {1}\n CurrentThread is background :{2}\n WorkerThreadNumber is:{3}\n IOThreadNumbers is: {4}\n",  
  47.                 data,  
  48.                 Thread.CurrentThread.ManagedThreadId,  
  49.                 Thread.CurrentThread.IsBackground.ToString(),  
  50.                 workthreadnumber.ToString(),  
  51.                 iothreadnumber.ToString());  
  52.         }  
  53.     }  
运行结果:[.Net线程处理系列]专题二:线程池中的工作者线程

 五、任务

同样 任务的引入也是为了解决通过ThreadPool.QueueUserWorkItem中限制的问题,

下面代码演示通过任务来实现异步:

5.1 使用任务来实现异步

  1. using System;  
  2. using System.Threading;  
  3. using System.Threading.Tasks;  
  4.  
  5. namespace TaskUse  
  6. {  
  7.     class Program  
  8.     {  
  9.         static void Main(string[] args)  
  10.         {  
  11.             ThreadPool.SetMaxThreads(1000, 1000);  
  12.             PrintMessage("Main Thread Start");   标题名称:[.Net线程处理系列]专题二:线程池中的工作者线程
    分享链接:http://kswjz.com/article/gjhopj.html
扫二维码与项目经理沟通

我们在微信上24小时期待你的声音

解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流