四、多线程的自动管理(线程池)

在多线程的程序中,经常会出现两种情况:

一种情况: 应用程序中,线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应

这一般使用ThreadPool(线程池)来解决;

另一种情况:线程平时都处于休眠状态,只是周期性地被唤醒

这一般使用Timer(定时器)来解决;

ThreadPool类提供一个由系统维护的线程池(可以看作一个线程的容器),该容器需要 Windows 2000 以上系统支持,因为其中某些方法调用了只有高版本的Windows才有的API函数。

将线程安放在线程池里,需使用ThreadPool.QueueUserWorkItem()方法,该方法的原型如下:

//将一个线程放进线程池,该线程的Start()方法将调用WaitCallback代理对象代表的函数

public static bool QueueUserWorkItem(WaitCallback);

//重载的方法如下,参数object将传递给WaitCallback所代表的方法

public static bool QueueUserWorkItem(WaitCallback, object);

ThreadPool类是一个静态类,你不能也不必要生成它的对象。而且一旦使用该方法在线程池中添加了一个项目,那么该项目将是无法取消的。

在这里你无需自己建立线程,只需把你要做的工作写成函数,然后作为参数传递给ThreadPool.QueueUserWorkItem()方法就行了,传递的方法就是依靠WaitCallback代理对象,而线程的建立、管理、运行等工作都是由系统自动完成的,你无须考虑那些复杂的细节问题。

ThreadPool 的用法:

首先程序创建了一个ManualResetEvent对象,该对象就像一个信号灯,可以利用它的信号来通知其它线程。

本例中,当线程池中所有线程工作都完成以后,ManualResetEvent对象将被设置为有信号,从而通知主线程继续运行。

ManualResetEvent对象有几个重要的方法:

初始化该对象时,用户可以指定其默认的状态(有信号/无信号);

在初始化以后,该对象将保持原来的状态不变,直到它的Reset()或者Set()方法被调用:

Reset()方法:将其设置为无信号状态;

Set()方法:将其设置为有信号状态。

WaitOne()方法:使当前线程挂起,直到ManualResetEvent对象处于有信号状态,此时该线程将被激活。然后,程序将向线程池中添加工作项,这些以函数形式提供的工作项被系统用来初始化自动建立的线程。当所有的线程都运行完了以后,ManualResetEvent.Set()方法被调用,因为调用了ManualResetEvent.WaitOne()方法而处在等待状态的主线程将接收到这个信号,于是它接着往下执行,完成后边的工作。

ThreadPool 的用法示例:


  1. using System;
    using System.Collections;
    using System.Threading;

    namespace ThreadExample
    {
    //这是用来保存信息的数据结构,将作为参数被传递
    public class SomeState
    {
       public int Cookie;
       public SomeState(int iCookie)
       {
    Cookie = iCookie;
       }
    }

    public class Alpha
    {
      public Hashtable HashCount;
      public ManualResetEvent eventX;
      public static int iCount = 0;
      public static int iMaxCount = 0;
      
    public Alpha(int MaxCount)
      {
      HashCount = new Hashtable(MaxCount);
      iMaxCount = MaxCount;
      }

      //线程池里的线程将调用Beta()方法
      public void Beta(Object state)
      {
       //输出当前线程的hash编码值和Cookie的值
      Console.WriteLine(" {0} {1} :", Thread.CurrentThread.GetHashCode(),((SomeState)state).Cookie);
    Console.WriteLine("HashCount.Count=={0}, Thread.CurrentThread.GetHashCode()=={1}", HashCount.Count, Thread.CurrentThread.GetHashCode());
    lock (HashCount)
    {
         //如果当前的Hash表中没有当前线程的Hash值,则添加之
         if (!HashCount.ContainsKey(Thread.CurrentThread.GetHashCode()))
         HashCount.Add (Thread.CurrentThread.GetHashCode(), 0);
         HashCount[Thread.CurrentThread.GetHashCode()] =
    ((int)HashCount[Thread.CurrentThread.GetHashCode()])+1;
       }
    int iX = 2000;
    Thread.Sleep(iX);
    //Interlocked.Increment()操作是一个原子操作,具体请看下面说明
    Interlocked.Increment(ref iCount);

    if (iCount == iMaxCount)
    {
        Console.WriteLine();
         Console.WriteLine("Setting eventX ");
         eventX.Set();
      }
       }
    }

    public class SimplePool
    {
    public static int Main(string[] args)
    {
    Console.WriteLine("Thread Pool Sample:");
    bool W2K = false;
    int MaxCount = 10;//允许线程池中运行最多10个线程
    //新建ManualResetEvent对象并且初始化为无信号状态
    ManualResetEvent eventX = new ManualResetEvent(false);
    Console.WriteLine("Queuing {0} items to Thread Pool", MaxCount);
    Alpha oAlpha = new Alpha(MaxCount);
    //创建工作项
    //注意初始化oAlpha对象的eventX属性
    oAlpha.eventX = eventX;
    Console.WriteLine("Queue to Thread Pool 0");
    try
    {
    //将工作项装入线程池
    //这里要用到Windows 2000以上版本才有的API,所以可能出现NotSupportException异常
    ThreadPool.QueueUserWorkItem(new WaitCallback(oAlpha.Beta), new SomeState(0));
    W2K = true;
    }
    catch (NotSupportedException)
    {
    Console.WriteLine("These API's may fail when called on a non-Windows 2000 system.");
    W2K = false;
    }
    if (W2K)//如果当前系统支持ThreadPool的方法.
    {
    for (int iItem=1;iItem < MaxCount;iItem++)
    {
    //插入队列元素
    Console.WriteLine("Queue to Thread Pool {0}", iItem);
    ThreadPool.QueueUserWorkItem(new WaitCallback(oAlpha.Beta), new SomeState(iItem));
    }
    Console.WriteLine("Waiting for Thread Pool to drain");
    //等待事件的完成,即线程调用ManualResetEvent.Set()方法
    eventX.WaitOne(Timeout.Infinite,true);
    //WaitOne()方法使调用它的线程等待直到eventX.Set()方法被调用
    Console.WriteLine("Thread Pool has been drained (Event fired)");
    Console.WriteLine();
    Console.WriteLine("Load across threads");
    foreach(object o in oAlpha.HashCount.Keys)
    Console.WriteLine("{0} {1}", o, oAlpha.HashCount[o]);
    }
    Console.ReadLine();
    return 0;
    }
    }
    }
    }

程序中应该引起注意的地方:

SomeState类是一个保存信息的数据结构,它在程序中作为参数被传递给每一个线程,因为你需要把一些有用的信息封装起来提供给线程,而这种方式是非常有效的。

程序出现的InterLocked类也是专为多线程程序而存在的,它提供了一些有用的原子操作。

原子操作:就是在多线程程序中,如果这个线程调用这个操作修改一个变量,那么其他线程就不能修改这个变量了,这跟lock关键字在本质上是一样的。


  1. Thread Pool Sample:
    Queuing 10 items to Thread Pool
    Queue to Thread Pool 0
    Queue to Thread Pool 1
    Queue to Thread Pool 2
    Queue to Thread Pool 3
    Queue to Thread Pool 4
    Queue to Thread Pool 5
    2 0 :
    HashCount.Count==0, Thread.CurrentThread.GetHashCode()==2
    Queue to Thread Pool 6
    Queue to Thread Pool 7
    Queue to Thread Pool 8
    Queue to Thread Pool 9
    Waiting for Thread Pool to drain
    4 1 :
    HashCount.Count==1, Thread.CurrentThread.GetHashCode()==4
    6 2 :
    HashCount.Count==1, Thread.CurrentThread.GetHashCode()==6
    7 3 :
    HashCount.Count==1, Thread.CurrentThread.GetHashCode()==7
    2 4 :
    HashCount.Count==1, Thread.CurrentThread.GetHashCode()==2
    8 5 :
    HashCount.Count==2, Thread.CurrentThread.GetHashCode()==8
    9 6 :
    HashCount.Count==2, Thread.CurrentThread.GetHashCode()==9
    10 7 :
    HashCount.Count==2, Thread.CurrentThread.GetHashCode()==10
    11 8 :
    HashCount.Count==2, Thread.CurrentThread.GetHashCode()==11
    4 9 :
    HashCount.Count==2, Thread.CurrentThread.GetHashCode()==4

    Setting eventX
    Thread Pool has been drained (Event fired)

    Load across threads
    11 1
    10 1
    9 1
    8 1
    7 1
    6 1
    4 2
    2 2

我们应该彻底地分析上面的程序,把握住线程池的本质,理解它存在的意义是什么,这样才能得心应手地使用它。

五、多线程的自动管理(定时器)

Timer类:设置一个定时器,定时执行用户指定的函数。

定时器启动后,系统将自动建立一个新的线程,执行用户指定的函数。

初始化一个Timer对象:

Timer timer = new Timer(timerDelegate, s,1000, 1000);

// 第一个参数:指定了TimerCallback 委托,表示要执行的方法;

// 第二个参数:一个包含回调方法要使用的信息的对象,或者为空引用;

// 第三个参数:延迟时间——计时开始的时刻距现在的时间,单位是毫秒,指定为“0”表示立即启动计时器;

// 第四个参数:定时器的时间间隔——计时开始以后,每隔这么长的一段时间,TimerCallback所代表的方法将被调用一次,单位也是毫秒。指定 Timeout.Infinite 可以禁用定期终止。

Timer.Change()方法:修改定时器的设置。(这是一个参数类型重载的方法)

使用示例: timer.Change(1000,2000);

Timer类的程序示例(来源:MSDN):


  1. using System;
    using System.Threading;

    namespace ThreadExample
    {
    class TimerExampleState
    {
       public int counter = 0;
       public Timer tmr;
    }

    class App
    {
       public static void Main()
       {
       TimerExampleState s = new TimerExampleState();

       //创建代理对象TimerCallback,该代理将被定时调用
       TimerCallback timerDelegate = new TimerCallback(CheckStatus);

    //创建一个时间间隔为1s的定时器
    Timer timer = new Timer(timerDelegate, s,1000, 1000);
    s.tmr = timer;

    //主线程停下来等待Timer对象的终止
    while(s.tmr != null)
    Thread.Sleep(0);
    Console.WriteLine("Timer example done.");
    Console.ReadLine();
       }

       //下面是被定时调用的方法
       static void CheckStatus(Object state)
       {
    TimerExampleState s =(TimerExampleState)state;
    s.counter++;
    Console.WriteLine("{0} Checking Status {1}.",DateTime.Now.TimeOfDay, s.counter);

    if(s.counter == 5)
    {
    //使用Change方法改变了时间间隔
    (s.tmr).Change(10000,2000);
    Console.WriteLine("changed");
    }

    if(s.counter == 10)
    {
    Console.WriteLine("disposing of timer");
    s.tmr.Dispose();
    s.tmr = null;
    }
       }
    }
    }

程序首先创建了一个定时器,它将在创建1秒之后开始每隔1秒调用一次CheckStatus()方法,当调用5次以后,在CheckStatus()方法中修改了时间间隔为2秒,并且指定在10秒后重新开始。当计数达到10次,调用Timer.Dispose()方法删除了timer对象,主线程于是跳出循环,终止程序。

六、互斥对象

如何控制好多个线程相互之间的联系,不产生冲突和重复,这需要用到互斥对象,即:System.Threading 命名空间中的 Mutex 类。

我们可以把Mutex看作一个出租车,乘客看作线程。乘客首先等车,然后上车,最后下车。当一个乘客在车上时,其他乘客就只有等他下车以后才可以上车。而线程与Mutex对象的关系也正是如此,线程使用Mutex.WaitOne()方法等待Mutex对象被释放,如果它等待的Mutex对象被释放了,它就自动拥有这个对象,直到它调用Mutex.ReleaseMutex()方法释放这个对象,而在此期间,其他想要获取这个Mutex对象的线程都只有等待。

下面这个例子使用了Mutex对象来同步四个线程,主线程等待四个线程的结束,而这四个线程的运行又是与两个Mutex对象相关联的。

其中还用到AutoResetEvent类的对象,可以把它理解为一个信号灯。这里用它的有信号状态来表示一个线程的结束。

// AutoResetEvent.Set()方法设置它为有信号状态

// AutoResetEvent.Reset()方法设置它为无信号状态

Mutex 类的程序示例:


  1. using System;
    using System.Threading;

    namespace ThreadExample
    {
    public class MutexSample
    {
       static Mutex gM1;
       static Mutex gM2;
       const int ITERS = 100;
       static AutoResetEvent Event1 = new AutoResetEvent(false);
       static AutoResetEvent Event2 = new AutoResetEvent(false);
       static AutoResetEvent Event3 = new AutoResetEvent(false);
       static AutoResetEvent Event4 = new AutoResetEvent(false);

       public static void Main(String[] args)
       {
    Console.WriteLine("Mutex Sample ");
    //创建一个Mutex对象,并且命名为MyMutex
    gM1 = new Mutex(true,"MyMutex");
    //创建一个未命名的Mutex 对象.
    gM2 = new Mutex(true);
    Console.WriteLine(" - Main Owns gM1 and gM2");

    AutoResetEvent[] evs = new AutoResetEvent[4];
    evs[0] = Event1; //为后面的线程t1,t2,t3,t4定义AutoResetEvent对象
    evs[1] = Event2;
    evs[2] = Event3;
    evs[3] = Event4;

    MutexSample tm = new MutexSample( );
    Thread t1 = new Thread(new ThreadStart(tm.t1Start));
    Thread t2 = new Thread(new ThreadStart(tm.t2Start));
    Thread t3 = new Thread(new ThreadStart(tm.t3Start));
    Thread t4 = new Thread(new ThreadStart(tm.t4Start));
    t1.Start( );// 使用Mutex.WaitAll()方法等待一个Mutex数组中的对象全部被释放
    t2.Start( );// 使用Mutex.WaitOne()方法等待gM1的释放
    t3.Start( );// 使用Mutex.WaitAny()方法等待一个Mutex数组中任意一个对象被释放
    t4.Start( );// 使用Mutex.WaitOne()方法等待gM2的释放

    Thread.Sleep(2000);
    Console.WriteLine(" - Main releases gM1");
    gM1.ReleaseMutex( ); //线程t2,t3结束条件满足

    Thread.Sleep(1000);
    Console.WriteLine(" - Main releases gM2");
    gM2.ReleaseMutex( ); //线程t1,t4结束条件满足

    //等待所有四个线程结束
    WaitHandle.WaitAll(evs);
    Console.WriteLine(" Mutex Sample");
    Console.ReadLine();
       }

       public void t1Start( )
       {
    Console.WriteLine("t1Start started, Mutex.WaitAll(Mutex[])");
    Mutex[] gMs = new Mutex[2];
    gMs[0] = gM1;//创建一个Mutex数组作为Mutex.WaitAll()方法的参数
    gMs[1] = gM2;
    Mutex.WaitAll(gMs);//等待gM1和gM2都被释放
    Thread.Sleep(2000);
    Console.WriteLine("t1Start finished, Mutex.WaitAll(Mutex[]) satisfied");
    Event1.Set( ); //线程结束,将Event1设置为有信号状态
       }
       public void t2Start( )
       {
    Console.WriteLine("t2Start started, gM1.WaitOne( )");
    gM1.WaitOne( );//等待gM1的释放
    Console.WriteLine("t2Start finished, gM1.WaitOne( ) satisfied");
    Event2.Set( );//线程结束,将Event2设置为有信号状态
       }
       public void t3Start( )
       {
    Console.WriteLine("t3Start started, Mutex.WaitAny(Mutex[])");
    Mutex[] gMs = new Mutex[2];
    gMs[0] = gM1;//创建一个Mutex数组作为Mutex.WaitAny()方法的参数
    gMs[1] = gM2;
    Mutex.WaitAny(gMs);//等待数组中任意一个Mutex对象被释放
    Console.WriteLine("t3Start finished, Mutex.WaitAny(Mutex[])");
    Event3.Set( );//线程结束,将Event3设置为有信号状态
       }
       public void t4Start( )
       {
    Console.WriteLine("t4Start started, gM2.WaitOne( )");
    gM2.WaitOne( );//等待gM2被释放
    Console.WriteLine("t4Start finished, gM2.WaitOne( )");
    Event4.Set( );//线程结束,将Event4设置为有信号状态
       }
    }
    }

程序的输出结果:


  1. Mutex Sample
    - Main Owns gM1 and gM2
    t1Start started, Mutex.WaitAll(Mutex[])
    t2Start started, gM1.WaitOne( )
    t3Start started, Mutex.WaitAny(Mutex[])
    t4Start started, gM2.WaitOne( )
    - Main releases gM1
    t2Start finished, gM1.WaitOne( ) satisfied
    t3Start finished, Mutex.WaitAny(Mutex[])
    - Main releases gM2
    t1Start finished, Mutex.WaitAll(Mutex[]) satisfied
    t4Start finished, gM2.WaitOne( )
    Mutex Sample

从执行结果可以很清楚地看到,线程t2,t3的运行是以gM1的释放为条件的,而t4在gM2释放后开始执行,t1则在gM1和gM2都被释放了之后才执行。Main()函数最后,使用WaitHandle等待所有的AutoResetEvent对象的信号,这些对象的信号代表相应线程的结束。

本文出处:xugang http://www.cnblogs.com/xugang/archive/2008/03/23/1118530.html

版权声明:本文为博主原创文章,未经博主允许不得转载。

C#多线程(下) 分类: C# 线程 2015-03-09 10:41 153人阅读 评论(0) 收藏的更多相关文章

  1. 全面解析sizeof(下) 分类: C/C++ StudyNotes 2015-06-15 10:45 263人阅读 评论(0) 收藏

    以下代码使用平台是Windows7 64bits+VS2012. sizeof作用于基本数据类型,在特定的平台和特定的编译中,结果是确定的,如果使用sizeof计算构造类型:结构体.联合体和类的大小时 ...

  2. 全面解析sizeof(上) 分类: C/C++ StudyNotes 2015-06-15 10:18 188人阅读 评论(0) 收藏

    以下代码使用平台是Windows7 64bits+VS2012. sizeof是C/C++中的一个操作符(operator),其作用就是返回一个对象或者类型所占的内存字节数,使用频繁,有必须对齐有个全 ...

  3. 菜鸟学习-C语言函数参数传递详解-结构体与数组 分类: C/C++ Nginx 2015-07-14 10:24 89人阅读 评论(0) 收藏

    C语言中结构体作为函数参数,有两种方式:传值和传址. 1.传值时结构体参数会被拷贝一份,在函数体内修改结构体参数成员的值实际上是修改调用参数的一个临时拷贝的成员的值,这不会影响到调用参数.在这种情况下 ...

  4. JavaScript、Ajax与jQuery的关系 分类: C1_HTML/JS/JQUERY 2014-07-31 10:15 3388人阅读 评论(0) 收藏

    简单总结: 1.JS是一门前端语言. 2.Ajax是一门技术,它提供了异步更新的机制,使用客户端与服务器间交换数据而非整个页面文档,实现页面的局部更新. 3.jQuery是一个框架,它对JS进行了封装 ...

  5. 提高编程能力的7条建议 分类: T_TALENT 2014-04-12 10:41 294人阅读 评论(0) 收藏

    编程是非常酷的一件事情,但是在酷炫的背后它对很多人来说还是挺难的.很多人在学习编程之初就被困难击败了. 当你不熟悉编程的时候,你可能会觉得无从下手,并且不知道如何运用学到的知识.只要你通过了这一困难的 ...

  6. MS SQL 合并结果集并求和 分类: SQL Server 数据库 2015-02-13 10:59 92人阅读 评论(0) 收藏

    业务情景:有这样一张表:其中Id列为表主键,Name为用户名,State为记录的状态值,Note为状态的说明,方便阅读. 需求描述:需要查询出这样的结果:某个人某种状态的记录数,如:张三,待审核记录数 ...

  7. Nginx介绍 分类: Nginx 服务器搭建 2015-07-13 10:50 19人阅读 评论(0) 收藏

    海量请求,高性能服务器. 对比Apache, Apache:稳定,开源,跨平台,重量级,不支持高度并发的web服务器. 由此,出现了Lighttpd与Nignx:轻量级,高性能. 发音:engine ...

  8. jQuery中的on()和click()的区别 分类: 前端 HTML jQuery 2014-11-06 10:26 96人阅读 评论(0) 收藏

    HTML页面代码 <div> <h1>Click</h1> <button class="add">Click me to add ...

  9. 改变HTML中超链接的显示样式 分类: C1_HTML/JS/JQUERY 2014-08-27 10:11 595人阅读 评论(0) 收藏

    更详细的内容请参考:http://www.w3school.com.cn/tags/tag_a.asp HTML中的代码如下: <a class="news_title" t ...

随机推荐

  1. 【实习记】2014-08-24实习生无法映射磁盘替代方案rsync+非默认端口22设置

    正职开发人员有两个电脑,一个办公网的,一个开发网的.通过samba服务在开发网机器上映射编译环境机的磁盘没有问题. 开发岗实习生使用虚拟机做跳板方式登录编译环境机.上面的方法不能用. 替代方法:rsy ...

  2. Java之网络请求工具类(依赖:org.apache.http;注:HttpClient 4.4,HttpCore 4.4)

    到此处可以去下载依赖包:http://hc.apache.org/downloads.cgi import java.util.List; import org.apache.http.HttpSta ...

  3. 《Velocity 模板使用指南》中文版[转]

    转自:http://blog.csdn.net/javafound/archive/2007/05/14/1607931.aspx <Velocity 模板使用指南>中文版 源文见 htt ...

  4. 修改Tomcat命令窗口的名字

    在运行多个tomcat窗口的时候,可以通过修改tomcat命令窗口的名字来区分不同的tomcat,修改如下: 找到tomcat下面的这个文件:tomcat_home\bin\catalina.bat, ...

  5. 制作font-icon有感

    连日来有些空闲,趁着这闲余时间,我尝试亲自制作一些Font-Icon,让以后可以运用到工作中.但是基于本人水平有限,PS操作只能以非常基础来形容,而AI呢,根本就只会放大操作.在这过程真的非常感谢设计 ...

  6. 精通 Oracle+Python,第 2 部分:处理时间和日期

    从 Python 2.4 版开始,cx_Oracle 自身可以处理 DATE 和 TIMESTAMP 数据类型,将这些列的值映射到 Python 的 datetime 模块的 datetime 对象中 ...

  7. 【python】python支持中文变量,醉了

    哈哈 = 1 呜呜 = -1 哈哈 + 呜呜 = 0

  8. 2016022613 - redis连接命令集合

    redis连接命令 1.ping 用途:检查服务器是否正在运行 返回数据pong,表示服务器在运行. 2.quit 用途:关掉当前服务器连接 3.auth password 用途:服务器验证密码 没有 ...

  9. ASP.NET MVC轻教程 Step By Step 8——路由

    在前面的教程里,细心的你可能会有个疑问,就是地址栏输入/Home/Write就可以进入留言页面.无论是静态HTML还是ASP/ASP.NET.PHP,URL都是和某个页面相关.比如假设有个URL是“w ...

  10. Login过滤器

    继承自ActionFilterAttibute public override void OnActionExecuting(ActionExecutingContext filterContext) ...