C#多线程总结

1.几个重要的概念。

(1)进程:当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源。 而一个进程又是由多个线程所组成的。

(2)线程:线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数。

(3)多线程:多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

(4)任何程序在执行时,至少有一个主线程。

多线程的好处:可以提高CPU的利用率。在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,这样就大大提高了程序的效率。

多线程的缺点:线程也是程序,所以线程需要占用内存,线程越多占用内存也越多; 多线程需要协调和管理,所以需要CPU时间跟踪线程; 线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题; 线程太多会导致控制太复杂,最终可能造成很多Bug。

2.在.net framework class library中,所有与多线程机制应用相关的类都是放在System.Threading命名空间中的。如果你想在你的应用程序中使用多线程,就必须包含这个类。

Thread类有几个重要的方法,如下:

Start():启动线程;

Sleep(int):静态方法,暂停当前线程指定的毫秒数;

Abort():通常使用该方法来终止一个线程;

Suspend():该方法并不终止未完成的线程,它仅仅挂起线程,以后还可恢复;

Resume():恢复被Suspend()方法挂起的线程的执行。 

一个例子:

public static int Main(string[] args)
{
    Console.WriteLine("Thread Start/Stop/Join Sample");
    Thread nThread = new Thread(new ThreadStart(Thread1));
    nThread.Start();
    while (!nThread.IsAlive)
        Thread.Sleep(1);
    nThread.Abort();
    nThread.Join();//Join:阻塞调用线程,直到某个线程终止或经过了指定时间为止。Join代码写在哪,哪个就是调用线程;哪个线程执行了Join方法,则这个线程为某个线程。
    Console.WriteLine();
    Console.WriteLine("Thread1 has finished");
    try
    {
        Console.WriteLine("Try to restart the Thread1 thread");
        nThread.Start();
    }
    catch (ThreadStateException)
    {
        Console.Write("ThreadStateException trying to restart Thread1. ");
        Console.WriteLine("Expected since aborted threads cannot be restarted.");
        Console.ReadLine();
    }
    return 0;
}
public static void Thread1()
{
    while (true)
    {
        Console.WriteLine("Thread1 is running in its own thread.");
    }

} 

在Main()函数的While循环中,我们使用静态方法Thread.Sleep()让主线程停了1ms,这段时间CPU转向执行线程nThread。然后我们试图用Abort()方法终止线程nThread,后面的Join()方法使主线程等待,直到nThread线程结束。之后我们试图将nThread重新启动,但前面的Abort()方法结束线程是不可恢复的,最后程序抛出异常。

3.生产者和消费者

每个线程都有自己的资源 ,但是代码区是共享的,即每个线程都可以执行相同的函数。这可能带来的问题就是几个线程同时执行一个函数,导致数据的混乱,产生不可预料的结果,因些必须避免这种情况的发生。

(1)C#提供了关键字lock,它可以把一段代码定义为互斥段,互斥段在一个时刻内只允许一个线程进入执行,而其他线程必须等待。在C#中,关键字lock定义:lock(expression) statement_block。expression代表你希望跟踪的对象,通常是对象引用。如果你想保护一个类的实例,一般地使用this;如果你想保护一个静态变量,一般使用类名就可以了。 statement_block就是互斥段的代码,这段代码一个时刻内只可能被一个线程执行。

(2)当线程公用一个对象时,也会出现和公用代码类似的问题。这种问题就不应该使用lock关键字了,这里需要用Monitor,我们称之为监视器,Monitor提供了使线程共享资源的方案。Monitor可以锁定一个对象,一个线程只有得到这把锁才可以对该对象进行操作。Monitor必须和一个具体的对象相关联。当一个线程调用Monitor.Enter()方法锁定一个对象时,这个对象就归它所有了,其他线程想要访问这个对象,只有等待它使用Monitor.Exit()方法释放锁。

对于任何一个被Monitor锁定的对象,内存中都保存着与它相关的一些信息:

1>是现在持有锁的线程的引用;

2>是一个预备队列,队列中保存了已经准备好获取锁的线程;

3>是一个等待队列,队列中保存着当前正在等待这个对象状态改变的队列的引用。

当拥有对象锁的线程准备释放锁时,它使用Monitor.Pulse()方法通知等待队列中的第一个线程,于是该线程被转移到预备队列中,当对象锁被释放时,在预备队列中的线程可以立即获得对象锁。 

下面是如何使用lock和Monitor来实现线程的同步和通讯的例子。这个例子中,生产者线程和消费者线程是交替进行的,生产者写入一个数,消费者立即读取并显示。

首先,定义一个被操作的对象的类Cell,在这个类里,有两个方法:ReadFromCell()和WriteToCell。消费者线程将调用ReadFromCell()读取cellContents的内容并且显示出来,生产者进程将调用WriteToCell()方法向cellContents写入数据。

public class Cell
{
    int cellContents; // Cell对象里边的内容
    bool readerFlag = false// 状态标志,为true时可以读取,为false则正在写入
    public int ReadFromCell()
    {
        lock (this)
        {
            if (!readerFlag)//如果现在不可读取
            {
                try
                {
                    //等待WriteToCell方法中调用Monitor.Pulse()方法
                    Monitor.Wait(this);
                }
                catch (SynchronizationLockException e)
                {
                    Console.WriteLine(e);
                }
                catch (ThreadInterruptedException e)
                {
                    Console.WriteLine(e);
                }
            }
            Console.WriteLine("Consume: {0}", cellContents);
            readerFlag = false;//重置readerFlag标志,表示消费行为已经完成
            Monitor.Pulse(this);//通知WriteToCell()方法(该方法在另外一个线程中执行,等待中)
        }
        return cellContents;
    }
    public void WriteToCell(int n)
    {
        lock (this)
        {
            if (readerFlag)
            {
                try
                {
                    Monitor.Wait(this);
                }
                catch (SynchronizationLockException e)
                {
                    //当同步方法(指Monitor类除Enter之外的方法)在非同步的代码区被调用
                    Console.WriteLine(e);
                }
                catch (ThreadInterruptedException e)
                {
                    //当线程在等待状态的时候中止 
                    Console.WriteLine(e);
                }
            }
            cellContents = n;
            Console.WriteLine("Produce: {0}", cellContents);
            readerFlag = true;
            Monitor.Pulse(this);//通知另外一个线程中正在等待的ReadFromCell()方法
        }
    }
}
//下面定义生产者类 CellProd 和消费者类 CellCons ,它们都只有一个方法ThreadRun(),以便在Main()函数中提供给线程的ThreadStart代理对象,作为线程的入口。
public class CellProd
{
    Cell cell; // 被操作的Cell对象
    int quantity = 1// 生产者生产次数,初始化为1 

    public CellProd(Cell box, int request)
    {
        //构造函数
        cell = box;
        quantity = request;
    }
    public void ThreadRun()
    {
        for (int looper = 1; looper <= quantity; looper++)
            cell.WriteToCell(looper); //生产者向操作对象写入信息
    }
}
public class CellCons
{
    Cell cell;
    int quantity = 1;
    public CellCons(Cell box, int request)
    {
        //构造函数
        cell = box;
        quantity = request;
    }
    public void ThreadRun()
    {
        int valReturned;
        for (int looper = 1; looper <= quantity; looper++)
            valReturned = cell.ReadFromCell();//消费者从操作对象中读取信息
    }

} 

然后在下面这个类MonitorSample的Main()函数中,我们要做的就是创建两个线程分别作为生产者和消费者,使用CellProd.ThreadRun()方法和CellCons.ThreadRun()方法对同一个Cell对象进行操作。

public static void Main(String[] args)
{
    int result = 0//一个标志位,如果是0表示程序没有出错,如果是1表明有错误发生
    Cell cell = new Cell();
    //下面使用cell初始化CellProd和CellCons两个类,生产和消费次数均为20次
    CellProd prod = new CellProd(cell, 20);
    CellCons cons = new CellCons(cell, 20);
    Thread producer = new Thread(new ThreadStart(prod.ThreadRun));
    Thread consumer = new Thread(new ThreadStart(cons.ThreadRun));
    //生产者线程和消费者线程都已经被创建,但是没有开始执行 
    try
    {
        producer.Start();
        consumer.Start();
        producer.Join();
        consumer.Join();
        Console.ReadLine();
    }
    catch (ThreadStateException e)
    {
        //当线程因为所处状态的原因而不能执行被请求的操作
        Console.WriteLine(e);
        result = 1;
    }
    catch (ThreadInterruptedException e)
    {
        //当线程在等待状态的时候中止
        Console.WriteLine(e);
        result = 1;
    }
    //尽管Main()函数没有返回值,但下面这条语句可以向父进程返回执行结果
    Environment.ExitCode = result;

} 

在这个例子中,同步是通过等待Monitor.Pulse()来完成的。首先生产者生产了一个值,而同一时刻消费者处于等待状态,直到收到生产者的“脉冲(Pulse)”通知它生产已经完成,此后消费者进入消费状态,而生产者开始等待消费者完成操作后将调用Monitor.Pulese()发出的“脉冲”。

4.多线程的自动管理(线程池)

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

(1)应用程序中,线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应这一般使用ThreadPool(线程池)来解决;

(2)另一种情况:线程平时都处于休眠状态,只是周期性地被唤醒这一般使用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代理对象,而线程的建立、管理、运行等工作都是由系统自动完成的,你无须考虑那些复杂的细节问题。

如果应用程序需要对线程进行特定的控制,则不适合使用线程池,需要创建并管理自己的线程。不适合使用线程池的情形包括:

— 如果需要使一个任务具有特定的优先级。

— 如果具有可能会长时间运行(并因此阻塞其他任务)的任务。

— 如果需要将线程放置到单线程单元中(线程池中的线程均处于多线程单元中)。

— 如果需要用永久标识来标识和控制线程,比如想使用专用线程来中止该线程,将其挂起或按名称发现它。 

用法: 

首先程序创建了一个ManualResetEvent对象,该对象就像一个信号灯,可以利用它的信号来通知其它线程。例1中,当线程池中所有线程工作都完成以后,ManualResetEvent对象将被设置为有信号,从而通知主线程继续运行。

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

初始化该对象时,用户可以指定其默认的状态(有信号/无信号);在初始化以后,该对象将保持原来的状态不变,直到它的Reset()或者Set()方法被调用:

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

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

WaitOne()方法:使当前线程挂起,直到ManualResetEvent对象处于有信号状态,此时该线程将被激活。 

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

例1:

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类是一个保存信息的数据结构,它在程序中作为参数被传递给每一个线程,因为你需要把一些有用的信息封装起来提供给线程,而这种方式是非常有效的。
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类也是专为多线程程序而存在的,它提供了一些有用的原子操作。原子操作:就是在多线程程序中,如果这个线程调用这个操作修改一个变量,那么其他线程就不能修改这个变量了,这跟lock关键字在本质上是一样的。
        Interlocked.Increment(ref iCount);
        if (iCount == iMaxCount)
        {
            Console.WriteLine();
            Console.WriteLine("Setting eventX ");
            eventX.Set();
        }
    }

} 

例2: 

// 存放要计算的数值的字段
static double number1 = -1;
static double number2 = -1;
public static void Main()
{
    // 获取线程池的最大线程数和维护的最小空闲线程数
    int maxThreadNum, portThreadNum;
    int minThreadNum;
    ThreadPool.GetMaxThreads(out maxThreadNum, out portThreadNum);
    ThreadPool.GetMinThreads(out minThreadNum, out portThreadNum);
    Console.WriteLine("最大线程数:{0}", maxThreadNum);
    Console.WriteLine("最小空闲线程数:{0}", minThreadNum);
    // 函数变量值
    int x = 256;
    // 启动第一个任务:计算x的8次方
    Console.WriteLine("启动第一个任务:计算{0}的8次方。", x);
    ThreadPool.QueueUserWorkItem(new WaitCallback(TaskProc1), x);
    // 启动第二个任务:计算x的8次方根
    Console.WriteLine("启动第二个任务:计算{0}的8次方根。", x);
    ThreadPool.QueueUserWorkItem(new WaitCallback(TaskProc2), x);
    // 等待,直到两个数值都完成计算
    while (number1 == -1 || number2 == -1) ;

    // 打印计算结果
    Console.WriteLine("{0}的8次方 = {1}", x, number1);
    Console.WriteLine("{0}的8次方根 = {1}", x, number2);
    Console.ReadLine();
}
// 启动第一个任务:计算x的8次方
static void TaskProc1(object o)
{
    number1 = Math.Pow(Convert.ToDouble(o), 8);
}
// 启动第二个任务:计算x的8次方根
static void TaskProc2(object o)
{
    number2 = Math.Pow(Convert.ToDouble(o), 1.0 / 8.0);

} 

5.多线程自动管理(定时器)

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

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

初始化一个Timer对象:

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

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

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

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

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

Timer.Change()方法:修改定时器的设置。 

class TimerExampleState
{
    public int counter = 0;
    public Timer tmr;
}
public static void Main()
{
    TimerExampleState s = new TimerExampleState();
    //创建代理对象TimerCallback,该代理将被定时调用
    TimerCallback timerDelegate = new TimerCallback(CheckStatus);
    //创建一个时间间隔为1s的定时器
    Timer timer = new Timer(timerDelegate, s, 10001000);
    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(100002000);
        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对象,主线程于是跳出循环,终止程序。

6.互斥对象

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

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

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

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

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

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

例:

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设置为有信号状态
            gM1.ReleaseMutex();
            gM2.ReleaseMutex();
        }
        public void t2Start()
        {
            Console.WriteLine("t2Start started, gM1.WaitOne( )");
            gM1.WaitOne();//等待gM1的释放
            Console.WriteLine("t2Start finished, gM1.WaitOne( ) satisfied");
            Event2.Set();//线程结束,将Event2设置为有信号状态
            gM1.ReleaseMutex();
        }
        public void t3Start()
        {
            Console.WriteLine("t3Start started, Mutex.WaitAny(Mutex[])");
            Mutex[] gMs = new Mutex[2];
            gMs[0] = gM1;//创建一个Mutex数组作为Mutex.WaitAny()方法的参数
            gMs[1] = gM2;
            int i=Mutex.WaitAny(gMs);//等待数组中任意一个Mutex对象被释放
            Console.WriteLine("t3Start finished, Mutex.WaitAny(Mutex[])");
            Event3.Set();//线程结束,将Event3设置为有信号状态
            gMs[i].ReleaseMutex();
        }
        public void t4Start()
        {
            Console.WriteLine("t4Start started, gM2.WaitOne( )");
            gM2.WaitOne();//等待gM2被释放
            Console.WriteLine("t4Start finished, gM2.WaitOne( )");
            Event4.Set();//线程结束,将Event4设置为有信号状态
        }
    }

} 


参考:http://kb.cnblogs.com/page/42528/

posted @ 2013-01-25 10:29  飛雲若雪  阅读(953)  评论(1编辑  收藏  举报