Events类:
与互斥和信号量对象一样,事件也是一个系统范围内的资源同步方法。为了从托管代码中使用系统事件,.net framework 在system.Threading名称空间中提供了ManualResetEvent、AutoResetEvent、ManualResetEventSlim和CountdownEvent类。
AutoResetEvent(bool initialState):构造函数,用一个指示是否将初始状态设置为终止的布尔值初始化该类的新实例。
false:无信号,子线程的WaitOne方法不会被自动调用
true:有信号,子线程的WaitOne方法会被自动调用
Reset ():将事件状态设置为非终止状态,导致线程阻止;如果该操作成功,则返回true;否则,返回false。
Set ():将事件状态设置为终止状态,允许一个或多个等待线程继续;如果该操作成功,则返回true;否则,返回false。
WaitOne(): 阻止当前线程,直到收到信号。
WaitOne(TimeSpan, Boolean) :阻止当前线程,直到当前实例收到信号,使用 TimeSpan 度量时间间隔并指定是否在等待之前退出同步域。
WaitAll():等待全部信号。
参考案例:
public class Request
{
//建立事件数组
public AutoResetEvent[] autoEvents = null;
public Request()
{
autoEvents = new AutoResetEvent[] {
new AutoResetEvent(false),
new AutoResetEvent(false)
};
}
public string MethodOne()
{
Console.WriteLine("执行方法一");
Task.Delay(2000).Wait();
//有信号
autoEvents[0].Set();
return "方法一执行完成";
}
public string MethodTwo()
{
Console.WriteLine("执行方法二");
Task.Delay(1000).Wait();
//有信号
autoEvents[1].Set();
return "方法二执行完成";
}
public void MethodThread(Task<string>[] t)
{
Console.WriteLine($"{t[0].Result}、{t[1].Result},准备执行第三个方法");
}
}
//调用
Request req = new Request();
Task<string> t1 = Task.Run(() => { return req.MethodOne(); });
Task<string> t2 = new Task<string>(req.MethodTwo);
t2.Start();
WaitHandle.WaitAll(req.autoEvents);
req.MethodThread(new Task<string>[] { t1, t2 });
//超市购物排队付款等待收银员通知付款
//付钱方法
static void pay()
{
string tip = Console.ReadLine();
if (tip == "next")
{
AutoReset.Set();//收银员发送付款通知
}
}
//调用方式
var t1 = Task.Run(() =>
{
Console.WriteLine("客户1等待付钱");
AutoReset.WaitOne();
Console.WriteLine("客户1付钱");
AutoReset.Set();
});
var t2 = Task.Run(() =>
{
Console.WriteLine("客户2等待付钱");
AutoReset.WaitOne(3000);
Console.WriteLine("客户2付钱");
});
pay();//发送通知
Console.ReadLine();
通过上述案例可以看出调用Set()方法向AutoResetEvent发信号。也可以使用Reset()方法使之返回不发信号的状态。但是,如果一线程在等待自动重置的事件发信号。当第一个线程的等待状态结束时,该事件会自动变为不发信号的状态。这样,如果多个线程在等待向事件发信号,就只有一个线程结束其等待状态,它不是等待时间最长的线程,而是优先级最高的线程。
ManualResetEvent类的使用
static ManualResetEvent AutoReset = new ManualResetEvent(false);
//付钱方法
static void pay()
{
AutoReset.Set();//收银员发送付款通知
}
var t1 = Task.Run(() =>
{
Console.WriteLine("客户1直接付钱");
AutoReset.WaitOne();
Console.WriteLine("客户1已经付钱");
});
var t2 = Task.Run(() =>
{
Console.WriteLine("客户2直接付钱");
AutoReset.WaitOne();
Console.WriteLine("客户2已经付钱");
});
pay();//发送通知
Console.ReadLine();
ManualResetEvent类的使用方法大体和上述的AutoResetEvent类似;主要区别是:AutoResetEvent一次只能通知一个等待任务,通知完成后自动关闭;ManualResetEvent一次可以通知多个等待的任务,但是要关闭需要手动调用reset()方法
1.ManualResetEvent 调用一次Set()后将允许恢复所有被阻塞线程。需手动在调用WaitOne()之后调用Reset()重置信号量状态为非终止,然后再次调用WaitOne()的时候才能继续阻塞线程,反之则不阻塞
2.AutoResetEvent,调用一次Set()只能继续被阻塞的一个线程,多次调用Set()才行,但不需手动调用Reset();再次调用WaitOne()的时候又能阻塞线程,也是和前者的区别。
ManualResetEventSlim类是ManualResetEvent的简化版
public class Calculator
{
private ManualResetEventSlim _mEvent;
public Calculator(ManualResetEventSlim mEvent)
{
_mEvent = mEvent;
}
public int Result { get; set; }
public void Calculation(int x, int y)
{
Console.WriteLine($"任务{Task.CurrentId}开始计算");
Task.Delay(new Random().Next(3000)).Wait();
Result = x + y;
Console.WriteLine($"任务{Task.CurrentId}准备好了");
_mEvent.Set();
}
}
static void Main(string[] args)
{
var taskCount = 4;
var mEvents = new ManualResetEventSlim[taskCount];
var waitHandles = new WaitHandle[taskCount];
var calcs = new Calculator[taskCount];
Stopwatch sw = new Stopwatch();
sw.Start();
Task[] tasks = new Task[taskCount];
for (int i = 0; i < tasks.Length; i++)
{
int il = i;
mEvents[i] = new ManualResetEventSlim(false);
waitHandles[i] = mEvents[i].WaitHandle;
calcs[i] = new Calculator(mEvents[i]);
Task.Run(() =>
{
calcs[il].Calculation(il + 1, il + 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($"完成任务{index},result:{calcs[index].Result}");
}
}
sw.Stop();
var times = sw.Elapsed.TotalSeconds;
Console.WriteLine("所消耗时间:" + times);
Console.ReadLine();
}
CountdownEvent类:在一个类似的场景中,为了把一些工作分支到多个任务中,并在以后合并结果,使用新的CountdownEvent类很有用。不需要为每个任务创建一个单独的事件对象,而只需要创建一个事件对象,CountdownEvent类为所有设置了事件的任务定义一个初始数字,在到达该计数后,就向CountdownEvent类发信号。
public class Calculator
{
private CountdownEvent _cEvent;
public Calculator(CountdownEvent cEvent)
{
_cEvent = cEvent;
}
public int Result { get; set; }
public void Calculation(Tuple<int, int> tuple)
{
Console.WriteLine($"任务{Task.CurrentId}开始计算");
Task.Delay(new Random().Next(3000)).Wait();
Result = tuple.Item1 + tuple.Item2;
Console.WriteLine($"任务{Task.CurrentId}准备好了");
_cEvent.Signal();
}
}
static void Main(string[] args)
{
var taskCount = 4;
var cEvent = new CountdownEvent(4);
var waitHandles = new WaitHandle[taskCount];
var calcs = new Calculator[taskCount];
Stopwatch sw = new Stopwatch();
sw.Start();
Task[] tasks = new Task[taskCount];
for (int i = 0; i < taskCount; i++)
{
calcs[i] = new Calculator(cEvent);
int il = i;
Task.Run(() => calcs[il].Calculation(Tuple.Create(il + 1, il + 3)));
}
cEvent.Wait();
Console.WriteLine("全部完成");
for (int i = 0; i < taskCount; i++)
{
Console.WriteLine($"任务{i},结果:{calcs[i].Result}");
}
sw.Stop();
var times = sw.Elapsed.TotalSeconds;
Console.WriteLine("所消耗时间:" + times);
Console.ReadLine();
}
events类相关的同步处理方式到此告一段落