多线程的一个问题(同步事件和等待句柄)转
同步事件和等待句柄用于解决更复杂的同步情况,比如一个一个大的计算步骤包含3个步骤result = first term + second term + third term,如果现在想写个多线程程序,同时计算first term,second term 和third term,等所有3个步骤计算好后再把它们汇总起来,我们就需要使用到同步事件和等待句柄,同步事件分有两个,分别为AutoResetEvent和ManualResetEvent,这两个类可以用来代表某个线程的运行状态:终止和非终止,等待句柄用来判断ResetEvent的状态,如果是非终止状态就一直等待,否则放行,让等待句柄下面的代码继续运行。下面的代码示例阐释了如何使用等待句柄来发送复杂数字计算的不同阶段的完成信号。此计算的格式为:result = first term + second term + third term ;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace PrintThreadSample
{
class CalculateTest
{
static void Main()
{
Calculate calc = new Calculate();
Console.WriteLine("Result = {0}.",calc.Result(234).ToString());
Console.WriteLine("Result = {0}.",calc.Result(55).ToString());
Console.Read();
}
}
class Calculate
{
double baseNumber, firstTerm, secondTerm, thirdTerm;
AutoResetEvent[] autoEvents;
ManualResetEvent manualEvent;
Random randomGenerator; // Generate random numbers to simulate the actual calculations.
public Calculate()
{
autoEvents = new AutoResetEvent[]
{
new AutoResetEvent(false),
new AutoResetEvent(false),
new AutoResetEvent(false)
};
manualEvent = new ManualResetEvent(false);
}
void CalculateBase(object stateInfo)
{
baseNumber = randomGenerator.NextDouble();
Console.WriteLine("baseNumber: {0}", baseNumber);
manualEvent.Set(); // Signal that baseNumber is ready.
}
// The following CalculateX methods all perform the same
// series of steps as commented in CalculateFirstTerm.
void CalculateFirstTerm(object stateInfo)
{
double preCalc = randomGenerator.NextDouble();// Perform a precalculation.
manualEvent.WaitOne(); // Wait for baseNumber to be calculated.
firstTerm = preCalc * baseNumber * randomGenerator.NextDouble();// Calculate the first term from preCalc and baseNumber.
Console.WriteLine("FirstTerm: {0}", firstTerm);
autoEvents[0].Set(); // Signal that the calculation is finished.
}
void CalculateSecondTerm(object stateInfo)
{
double preCalc = randomGenerator.NextDouble();
manualEvent.WaitOne();
secondTerm = preCalc * baseNumber * randomGenerator.NextDouble();
Console.WriteLine("SecondTerm: {0}", secondTerm);
autoEvents[1].Set();
}
void CalculateThirdTerm(object stateInfo)
{
double preCalc = randomGenerator.NextDouble();
manualEvent.WaitOne();
thirdTerm = preCalc * baseNumber * randomGenerator.NextDouble();
Console.WriteLine("ThirdTerm: {0}", thirdTerm);
autoEvents[2].Set();
}
public double Result(int seed)
{
randomGenerator = new Random(seed); // Simultaneously calculate the terms.
ThreadPool.QueueUserWorkItem(new WaitCallback(CalculateBase));
ThreadPool.QueueUserWorkItem(new WaitCallback(CalculateFirstTerm));
ThreadPool.QueueUserWorkItem(new WaitCallback(CalculateSecondTerm));
ThreadPool.QueueUserWorkItem(new WaitCallback(CalculateThirdTerm));
WaitHandle.WaitAll(autoEvents); // Wait for all of the terms to be calculated.
manualEvent.Reset(); // Reset the wait handle for the next calculation.
return firstTerm + secondTerm + thirdTerm;
}
}
}
该示例一共有4个ResetEvent,一个ManualEvent,三个AutoResetEvent,分别反映4个线程的运行状态。ManualEvent和AutoResetEvent有一点不同:AutoResetEvent是在当前线程调用set方法激活某线程之后,AutoResetEvent状态自动重置,而ManualEvent则需要手动调用Reset方法来重置状态。接着来看看上面那段代码的执行顺序,Main方法首先调用的是Result 方法,Result方法开启4个线程分别去执行,主线程阻塞在WaitHandle.WaitAll(autoEvents)处,等待3个计算步骤的完成。4个ResetEvent初始化状态都是非终止(构造实例时传入了false),CalculateBase首先执行完毕,其他3个线程阻塞在manualEvent.WaitOne()处,等待CalculateBase执行完成。CalculateBase生成baseNumber后,把代表自己的ManualEvent状态设置为终止状态。其他几个线程从manualEvent.WaitOne()处恢复执行,在执行完自己的代码后把自己对应的AutoResetEvent状态置为终止。当3个计算步骤执行完后,主线程从阻塞中恢复,把三个计算结果累加后返回。还要多补充一点的是WaitHandle的WaitOne,WaitAll,WaitAny方法,如果等待多个进程就用WaitAll,如本例中的:WaitHandle.WaitAll(autoEvents),WaitAny是等待的线程中有一个结束则停止等待。