Mutex类、Event类、SemaphoreSlim类和ReaderWriterLockSlim类等提供了多个进程之间的线程同步。

 1、WaitHandle 基类

  WaitHandle抽象类,用于等待一个信号的设置。可以根据其派生类的不同,等待不同的信号。异步委托的BeginInvoke()方法返回一个实现了IAsycResult接口的对象。使用IAsycResult接口可以用AsycWaitHandle属性访问WaitHandle基类。在调用WaitOne()方法时,线程会等待接收一个和等待句柄相关的信号:

static void Main(string[] args)
{
    Func<int> func = new Func<int>(
        () =>
        {
            Thread.Sleep(1500);
            return 1;
        });
    IAsyncResult ar = func.BeginInvoke(null, null);
    int count = 0;
    while (true)
    {
        Interlocked.Increment(ref count);
        Console.WriteLine("第{0}周期循环等待结果。", count);
        if (ar.AsyncWaitHandle.WaitOne(100, false))
        {
            Console.WriteLine("获得返回结果。");
            break;
        }
    }
    int result = func.EndInvoke(ar);
    Console.WriteLine("结果为:{0}", result);
}

  使用WaitHandle基类可以等待一个信号的出现(WaitHandle()方法)、等待多个对象都必须发出信号(WaitAll()方法)、等待多个对象中任一一个发出信号(WaitAny()方法)。其中WaitAll()方法和WaitAny()方法时WaitHandle类的静态方法,接收一个WaitHandle参数数组。

  WaitHandle基类的SafeWaitHandle属性,其中可以将一个本机句柄赋予一个系统资源,等待该句柄,如I/O操作,或者自定义的句柄。

2、Mutex 类

  Mutex类继承自WaitHandle类,提供跨多个进程同步访问的一个类。类似于Monitor类,只能有一个线程拥有锁定。在Mutex类的构造函数各参数含义:

  • initiallyOwned: 如果为 true,则给予调用线程已命名的系统互斥体的初始所属权(如果已命名的系统互斥体是通过此调用创建的);否则为 false。
  • name:系统互斥体的名称。 如果值为 null,则 System.Threading.Mutex 是未命名的。
  • createdNew: false。 该参数未经初始化即被传递。
  • mutexSecurity: 一个 System.Security.AccessControl.MutexSecurity 对象,表示应用于已命名的系统互斥体的访问控制安全性。

  互斥也可以在另一个进程中定义,操作系统能够识别有名称的互斥,它由进程之间共享。如果没有指定互斥的名称,则不在不同的进程之间共享。该方法可以检测程序是否已运行,可以禁止程序启动两次。

static void Main(string[] args)
{
    // ThreadingTimer();
    // TimersTimer();
    bool isCreateNew = false;
    Mutex mutex = new Mutex(false, "MyApp", out isCreateNew);//查询是否已有互斥“MyApp”存在
    if(isCreateNew==false)
    {
        //已存在互斥
    }
}

  要打开已有互斥,可以使用Mutex.OpenExisting()方法,不需要构造函数创建互斥时需要的相同.Net权限。可以使用WaitOne()方法获得互斥的锁定,成为该互斥的拥有着。调用ReleaseMutex()方法释放互斥:

if(mutex.WaitOne())//设置互斥锁定
{
    try
    {
        //执行代码
    }
    finally {
        mutex.ReleaseMutex();//释放互斥
    }
}
else
{
    //出现问题
}

 3、Semaphore 类

  信号量是一种计数的互斥锁定,可以同时由多个线程使用。信号量可定义允许同时访问受旗语锁定保护的资源的线程个数。Semaphore和SemaphoreSlim两个类具有信号量功能。Semaphore类可以指定名称,让其在系统资源范围内查找到,允许在不同的进程之间同步。Semaphore类是对较短等待时间进行优化了的轻型版本。

static void Main()
{
    int taskCount = 6;
    int semaphoreCount = 3;
    Semaphore semaphore = new Semaphore(semaphoreCount, semaphoreCount, "Test");//创建计数为3的信号量
    /* 第一个参数为初始释放的锁定数,第二个参数为可锁定的总数。如果第一个参数小于第二个参数,其差值就是已分配线程的计量数。
     * 第三个参数为信号指定的名称,能让它在不同的进程之间共享。
     */
    var tasks = new Task[taskCount];

    for (int i = 0; i < taskCount; i++)
    {
        tasks[i] = Task.Run(() => TaskMain(semaphore));//创建6个任务
    }

    Task.WaitAll(tasks);

    Console.WriteLine("All tasks finished");
}

//锁定信号的任务
static void TaskMain(Semaphore semaphore)
{
    bool isCompleted = false;
    while (!isCompleted)//循环等待被释放的信号量
    {
        if (semaphore.WaitOne(600))//最长等待600ms
        {
            try
            {
                Console.WriteLine("Task {0} locks the semaphore", Task.CurrentId);
                Thread.Sleep(2000);//2s后释放信号
            }
            finally
            {
                Console.WriteLine("Task {0} releases the semaphore", Task.CurrentId);
                semaphore.Release();//释放信号量
                isCompleted = true;
            }
        }
        else
        {
            //超过规定的等待时间,写入一条超时等待的信息
            Console.WriteLine("Timeout for task {0}; wait again", Task.CurrentId);
        }
    }
}

  以上方法中,信号量计数为3,因此最多只有三个任务可获得锁定,第4个及以后的任务必须等待。在解除锁定时,任何情况下一定要解除资源的锁定。

4、Events 类

  事件也是一个系统范围内资源同步的方法。主要由以下几个类提供:ManualResetEvent、AutoResetEvent、ManualResetEventSlim、和CountdownEvent类。

  ManualResetEventSlim类中,调用Set()方法可以发出信号;调用Reset()方法可以使重置为无信号状态。如果多个线程在等待向一个事件发出信号,并调用Set()方法,就释放所有等待线程。如果一个线程刚刚调用了WiatOne()方法,但事件已发出信号,等待的线程就可以继续等待。

  AutoResetEvent类中,同样可以通过Set()方法发出信号、Reset()方法重置信号,但是该类是自动重置信号。如果一个线程在等待自动重置的事件发信号,当第一个线程的等待状态结束时,该事件会自动变为不发信号的状态。即:如果多个线程在等待向事件发信号,只有一个线程结束其等待状态,它不是等待事件最长的线程,而是优先级最高的线程。

//计算数据的类,使用ManualResetEventSlim类的示例
public class Calculator
{
    private ManualResetEventSlim mEvent;
    public int Result { get; private set; }
    public Calculator(ManualResetEventSlim ev)
    {
        this.mEvent = ev;
    }
    public void Calculation(int x, int y)
    {
        Console.WriteLine("Task {0} starts calculation", Task.CurrentId);
        Thread.Sleep(new Random().Next(3000));//随机等待事件
        Result = x + y;//计算结果

        Console.WriteLine("Task {0} is ready", Task.CurrentId);
        mEvent.Set();//发出完成信号
    }
}
//外部调用的示例:
static void Main()
{
    const int taskCount = 10;

    ManualResetEventSlim[] mEvents = new ManualResetEventSlim[taskCount];

    WaitHandle[] waitHandles = new WaitHandle[taskCount];
    var calcs = new Calculator[taskCount];

    for (int i = 0; i < taskCount; i++)
    {
        int i1 = i;//目的是使后面要执行的Task不必等待执行完后才释放i,让for继续
        mEvents[i] = new ManualResetEventSlim(false);//对应任务的事件对象发出信号
        waitHandles[i] = mEvents[i].WaitHandle;//ManualResetEvent类派生自WaitHandle类,但ManualResetEventSlim并不是,因此需要保存其WaitHandle对象
        calcs[i] = new Calculator(mEvents[i]);

        Task.Run(() => calcs[i1].Calculation(i1 + 1, i1 + 3));
    }

    for (int i = 0; i < taskCount; i++)
    {
        int index = WaitHandle.WaitAny(waitHandles);//等待任何一个发出信号,并返回发出信号的索引
        if (index == WaitHandle.WaitTimeout)
        {
            Console.WriteLine("Timeout!!");
        }
        else
        {
            mEvents[index].Reset();//重新设置为无信号状态
            Console.WriteLine("finished task for {0}, result: {1}", index, calcs[index].Result);
        }
    }
}
View Code

相关文章: