C# ThreadPool学习笔记

摘要:
系列文章,从一个基本的代码说起,逐步探索 ThreadPool 的奥妙。

首先,看看线程池的样子:

从上图看出,线程池维护1个至n个线程,操作系统从请求队列中提取请求分配个线程池中的适合线程处理。

先写下如下的代码:

 

using  System;
using  System.Threading;

public   class  ThreadBase
{
    
public static void Main ( ) {

        System.Threading.WaitCallback waitCallback 
= new WaitCallback ( MyThreadWork );

        ThreadPool.QueueUserWorkItem ( waitCallback, 
"第一个线程" );
        ThreadPool.QueueUserWorkItem ( waitCallback, 
"第二个线程" );
        ThreadPool.QueueUserWorkItem ( waitCallback, 
"第三个线程" );
        ThreadPool.QueueUserWorkItem ( waitCallback, 
"第四个线程" );
        Console.ReadLine ( );
    }


    
public static void MyThreadWork ( object state ) {
        Console.WriteLine ( 
"线程现在开始启动…… {0}",(string)state );
        Thread.Sleep ( 
10000 );
        Console.WriteLine ( 
"运行结束…… {0}",( string ) state );
    }

}

分析上面的代码:
一、首先定义了一个 System.Threading.WaitCallback 对象 waitCallback。
WaitCallback 是一个委托,表示线程池线程要执行的回调方法,它的原型如下:

[ComVisibleAttribute( true )] 
public   delegate   void  WaitCallback (
    Object state
)

1、这里有一个 Callback 机制的问题,所谓的Callback 简单的理解就是由操作系统调用的函数,我们自己写的程序不需要调用。就像有个修理工到家里搞修理,你只需要告诉他钳子、螺丝刀、胶布等修理工具在什么地方就行,而不要管他在什么时候、在什么地方怎样使用这些修理工具。
2、WaitCallback 的参数" Object state",这个参数包含回调方法要使用的信息的对象。在接下来的情况中我再说。
既然 WaitCallback 委托的原型如此,那么我们就申明一个跟它的样子差不多的函数,这个函数就是要线程做的事情。

public   static    void  MyThreadWork (  object  state )


这里函数中多了一个 "static",这是因为Main的关系(更大一点就是因为C#语言机制的问题),如果WaitCallback 的对象不是在静态(static)方法中,这个static 是不需要的。

二、接着就是要执行的方法放入线程池中,以便操作系统执行。
我这里放置了四个方法要操作系统执行:

        ThreadPool.QueueUserWorkItem ( waitCallback,  " 第一个线程 "  );
        ThreadPool.QueueUserWorkItem ( waitCallback, 
" 第二个线程 "  );
        ThreadPool.QueueUserWorkItem ( waitCallback, 
" 第三个线程 "  );
        ThreadPool.QueueUserWorkItem ( waitCallback, 
" 第四个线程 "  );

这里,我放置到线程池中的操作是一样的,当然也可以不一样,接下来说。

三、最后阻塞主线程,等待线程池中的线程执行

 Console.ReadLine ( );

如果忽略掉这个代码,则有可能看不到任何输出

好了,这是主线程做的事情了,接下来看看线程池中的线程做的事情。
这个很简单,就是将线程的参数输出,然后线程睡眠(sleep)一段时间,最后输出线程结束的信息。

 

摘要:
系列文章,从一个基本的代码说起,逐步探索 ThreadPool 的奥妙。

在上次的基础上,我需要做以下的事情:
1、要传递给线程的参数更加复杂;
2、线程要处理的工作更加多样;
3、在线程中我要改变主线程传入的参数,并告诉主程序。

好的,第一个问题,因为 WaitCallback 委托的原型决定了参数只能有一个 ( Object state ),那没有办法,我们只能将多个参数封装到一个Object 中,也就是 class 中。
第二个问题,要处理这个问题也很简单,就是再定义一个 WaitCallback 委托的对象,将它作为参数传递给ThreadPool.QueueUserWorkItem ()方法。这个对象可以在线程里面处理另外一类的工作。
代码如下:

using  System;
using  System.Threading;

public   class  App
{
    
public static void Main ( ) {
        WaitCallback waitCallback 
= new WaitCallback ( MyThreadWork );

        WaitCallback waitCallback2
= new WaitCallback ( MyThreadWork2 );     // 增加线程工作类型

        ThreadPool.QueueUserWorkItem ( waitCallback, 
"第一个线程" );
        ThreadPool.QueueUserWorkItem ( waitCallback, 
"第二个线程" );

        MyState myState 
= new MyState ( "第三个线程",100);   // 增加自定义的线程参数类型
        ThreadPool.QueueUserWorkItem ( waitCallback2, myState );
        ThreadPool.QueueUserWorkItem ( waitCallback2, 
new MyState("第四个线程",2) );

        Console.WriteLine ( 
"MyState 的 Number 值为: {0}", myState.Number );    // 读取线程改变后的 MyState
        Console.ReadLine ( );
    }


    
public static void MyThreadWork ( object state ) {
        Console.WriteLine ( 
"MyThreadWork 开始启动 …… {0}", ( string ) state );
        Thread.Sleep ( 
10000 );
        Console.WriteLine ( 
"运行结束…… {0}", ( string ) state );
    }


    
// use MyState class 
    public static void MyThreadWork2 ( object state ) {
        Console.WriteLine ( 
"MyThreadWork2 开始启动…… {0},{1}", ( ( MyState ) state ).Info, ( ( MyState ) state ).Number );
        Thread.Sleep ( 
10000 );
        ( ( MyState ) state ).Number 
+= 1;      // 将 state的 Number 加 1
        Console.WriteLine ( "运行结束…… {0},{1}", ( ( MyState ) state ).Info, ( ( MyState ) state ).Number );
    }

}


public   class  MyState
{
    
private string info;
    
private int number;


    
public MyState ( string info, int number ) {
        
this.info = info;
        
this.number = number;
    }


    
public string Info {
        
get {
            
return this.info;
        }

        
set {
            
this.info = value;
        }

    }


    
public int Number {
        
get {
            
return this.number;
        }

        
set {
            
this.number = value;
        }

    }

}


在代码里面,我尝试对三个问题都进行解决,但是,很遗憾,上面的代码只是解决了第1、2个问题。
虽然,我的MyThreadWork2 () 线程尝试对 MyState 的 Number 进行加1操作,但是,主线程的输出仍然可能是 100,如下所示:

MyState 的 Number 值为:  100
MyThreadWork 开始启动 …… 第一个线程
MyThreadWork 开始启动 …… 第二个线程
MyThreadWork2 开始启动…… 第三个线程,
100
MyThreadWork2 开始启动…… 第四个线程,
2
运行结束…… 第一个线程
运行结束…… 第二个线程
运行结束…… 第三个线程,
101
运行结束…… 第四个线程,
3


光从代码看,我们的输出 MyState 信息的代码应该是后面执行,想不到它居然在线程启动之前就执行了!
呵呵,这就是多线程!
看样子,我需要控制我的主线程——等所有的线程池中的线程都执行完成后,才接着执行主线程中输出 MyState 信息的代码。这个,接下来再说。

三:

 上次解决了如何象线程传入参数,但是没有解决在主线程中获取发生了变化的参数的问题,引起这个问题的原因有两个:

1、线程池中的线程的启动、终止不是由我们设计的程序来控制的;

2、线程池中的线程执行完成后是没有返回值的。(这实际上与操作系统的线程调度和内存分配策略有关)

所以要想取得返回值,就需要解决这两个问题。

对于第一个问题,我们很容易想到操作系统提供的一种机制 “信号灯”(也有叫“信号量”、“信号”的),操作系统提供的这种机制允许进程之间进行通讯,呵呵,这样我们就可以让子线程与主线程进行通讯了。

对于第二个问题,由于线程没有返回值,我们可以传递给线程的参数中设置一个字段用于存储返回值,也可以通过通过获取 WaitCallBack 方法所在类的属性来获取(当然,在线程中这个属性是对其进行了改变的)。

现在,简单的说一下“信号灯”机制,打一个跟厕所有关的比喻,(不是很雅^_^),在火车上,当A上厕所的时候,他会将厕所的门锁上,对外标识一个“有人”的表示,当他解决完以后,他会打开门,“有人”的标识就会变成“无人”的标识,这样B就可以进去了,而如果A不把这个标识改变,B就算憋死也进不去的。(当然暴力方式排除在外)。C# 提供的 AutoResetEvent就是这样一种“信号灯”,定义这个类的变量,并传递到子线程,在子线程将结束时,将该“信号灯”设置为 true(“无人”)状态,这样主线程就可以进入了(开始执行)。

同样,我们还有上公共厕所的经历,在公共厕所了有很多个便池,当然还有一个厕所管理员,这时,A1,A2,A3……就可以在厕所管理员这里领取一个牌匾,同时进去解决“问题”, 如果,厕所管理员不是很bt,当A1出来的时候,他会放B1进去,但是也有Bt的,非得等到A1,A2,A3……都解决问题了,才让B1,B2,……进去,这就是WaitHandle.WaitAll()和WaitHandle.WaitAny(),WaitHandle.WaitOne()的区别,这是可以使用ManualResetEvent的“信号灯”

当然,还有一些线程之间也会争用资源,要达到同步的话,可以使用Monitor,Mutex的“信号灯”。

总之,信号灯机制提供了一个对子线程与主线程,子线程与子线程之间进行通讯的机制,这些通讯机制的共同特点是某个线程释放了资源(给出一个信号),其他的线程(主线程)才开始执行。

 

接着,我用一个端口扫描的程序来具体说明一下。

一个端口扫描程序就是对一段区域内的端口进行连接测试,如果能够连接上则标识这个端口是开放的。为了简化程序,我只是TCP连接方式的 端口进行扫描。为了加快扫描速度,我需要使用多线程,用ThreadPool对线程进行维护。

首先,定义一个端口扫描的类,该类实现对指定地址、指定端口进行扫描,并将扫描的结果(能、不能连接)保存 ,代码如下:(注意,为了加快端口扫描的速度,我对 Socket 的Connect采用异步方式,而信号灯等待1秒仍然没有得到结果,就认为没有连接到对方主机)


using System;
using System.Collections.Generic;
using System.Text;

using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace PortScaner
{
    
public class PortScaner
    {
        
private ManualResetEvent manualEvent;
        
private string hostName;
        
private bool connected;
        
private int port;

        
public bool Connected {
            
get { return connected; }
        }

        
public string HostName {
            
get { return hostName; }
            
set { hostName = value; }
        }
        

        
public int Port {
            
get { return port; }
            
set { port = value; }
        }

        
public PortScaner(string hostName, int port) {
            
this.hostName = hostName;
            
this.port = port;
            
this.connected = false;
            manualEvent 
= new ManualResetEvent(false);
        }


        
/// <summary>
        
/// 扫描指定主机的指定端口
        
/// </summary>
        
/// <returns></returns>
        public void  Scan( ) {

            Socket s 
= null;
            manualEvent.Reset();
            
try {
                s 
= new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                s.BeginConnect(
this.hostName, this.port, new AsyncCallback(EndConnected), s);
                
if (manualEvent.WaitOne(1000false== true) {
                    connected 
= s.Connected;
                }
                
else {
                    connected 
= false;
                }
            }
            
catch (Exception ) {
                connected 
= false;
            }
            
finally {
                
if (s != null) {
                    s.Close();
                    s 
= null;
                }
            }
        }

        
public void  EndConnected(IAsyncResult ar) {
            
try {
                Socket sk 
= (Socket)ar.AsyncState;
                sk.EndConnect(ar);
            }
            
catch (Exception) {
                connected 
= false;
            }
            
finally {
                manualEvent.Set();
            }
        }
    }
}

 

 接下来就是,在多线程的环境中执行Scan了,代码如下:


using System;
using System.Collections.Generic;
using System.Text;

using System.Threading;

namespace PortScaner
{
    
public class MyThreadState
    {
        
private PortScaner portScaner;
        
private AutoResetEvent autoEvent;

        
public PortScaner PortScaner {
            
get { return portScaner; }
            
set { portScaner = value; }
        }        

        
public AutoResetEvent AutoEvent {
            
get { return autoEvent; }
            
set { autoEvent = value; }
        }

        
public MyThreadState(PortScaner portScaner, AutoResetEvent autoEvent) {
            
this.portScaner = portScaner;
            
this.autoEvent = autoEvent;
        }


    }

    
public class PortScanerManager
    {
        
private string hostName;
        
private int startPort;
        
private int endPort;
        
private int maxThread;        

        
#region 属性和构造函数

        
public string HostName {
            
get { return hostName; }
            
set { hostName = value; }
        }        

        
public int StartPort {
            
get { return startPort; }
            
set { startPort = value; }
        }        

        
public int EndPort {
            
get { return endPort; }
            
set { endPort = value; }
        }        

        
public int MaxThread {
            
get { return maxThread; }
            
set {
                
if (value > 25)
                    maxThread 
= 25;
                
else 
                    maxThread 
= value; 
            }
        }
        
        
public PortScanerManager(string hostName) {
            
this.hostName = hostName;
            
this.startPort = 100;
            
this.endPort = 2000;
            
this.maxThread = 25;
        }

        
public PortScanerManager(string hostName, int startPort, int endPort) {
            
this.hostName = hostName;
            
this.startPort = startPort;
            
this.endPort = endPort;
            
this.maxThread = 25;
        }
        
#endregion

        
public List<int> Start() {

            List
<PortScaner > scaners = new List<PortScaner >();
            WaitCallback callBack 
= new WaitCallback(ThreadWork);

            
if (maxThread > endPort - startPort) {
                maxThread 
= endPort - startPort;
            }           
            ThreadPool.SetMaxThreads(maxThread, maxThread 
+ 10);

            
int runingThread = 0;
            
int curPort = startPort;

            
while (runingThread < endPort - startPort) {
                
int needThread = (endPort - curPort) > maxThread ? maxThread : (endPort - curPort);

                AutoResetEvent[] autoEvents 
= new AutoResetEvent[needThread];
                
for (int i = 0; i < needThread; i++) {
                    PortScaner scaner 
= new PortScaner(this.hostName, curPort);
                    scaners.Add(scaner);

                    autoEvents[i] 
= new AutoResetEvent(false);
                    MyThreadState state 
= new MyThreadState(scaner, autoEvents[i]);
                    ThreadPool.QueueUserWorkItem(callBack, state);

                    curPort
++;
                    runingThread
++;
                }

                WaitHandle.WaitAll (autoEvents);
            }       
    
            List
<int> result = new List<int> ();
            
for (int i = 0; i < scaners.Count; i++) {
                
if (scaners[i].Connected == true)
                    result.Add(scaners[i].Port);
            }
            
return result;
        }


        
private void ThreadWork(object state) {

            ((MyThreadState )state).PortScaner .Scan();
            ((MyThreadState)state).AutoEvent.Set();
        }
    }
}

 

呵呵,这里用了比较 bt 的办法, WaitHandle.WaitAll (autoEvents);这是为了前面讲的原理写的,而为了不那么bt可以采用推拉窗技术。

 

好了,就说了这么多,一则希望能对大家有所帮助,二则希望自己将来写程序的时候,能够很快的进入状态。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值