1、ManualResetEvent 和 ManualResetEventSlim
ManualResetEvent
表示线程同步事件,收到信号时,必须手动重置该事件。 此类不能被继承。ManualResetEventSlim
于实现更好的性能 ManualResetEvent
,而不是等待时间预计非常短,事件不会跨越进程边界。 等待事件收到信号期间,ManualResetEventSlim
会短暂使用忙碌旋转。 等待时间较短时,旋转的开销相对于使用等待句柄来进行等待的开销会少很多。 不过,如果在特定时间段内事件没有收到信号,ManualResetEventSlim
会求助于常规的事件句柄等待。从 .NET Framework 版本4.0 开始, System.Threading.ManualResetEventSlim
类是的轻型替代ManualResetEvent
。
2、ManualResetEvent的使用
参考文档:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.manualresetevent?view=net-6.0
new ManualResetEvent(false)
为创建在无信号状态。WaitOne()
方法会阻塞,调用Set()
方法设置为有信号状态,WaitOne()
方法不会阻塞,需要调用Reset()方法变为无信号状态,WaitOne()方法将会阻塞。new ManualResetEvent(true)为创建在有信号状态。WaitOne()
方法不会阻塞,需要调用Reset()
方法变为无信号状态,WaitOne()
方法将会阻塞。
例如,
using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace ConsoleApplication { class Program { //// mre用于手动阻塞和释放线程。false是创建在无信号状态。 private static ManualResetEvent mre = new ManualResetEvent(false); static void Main() { Console.WriteLine("启动 3个在ManualResetEvent上阻塞的命名线程"); for (int i = 0; i <= 2; i++) { Thread t = new Thread(ThreadProc); t.Name = "Thread_" + i; t.Start(); } Thread.Sleep(500); Console.WriteLine("按回车键,当三个线程都启动后,调用Set()释放所有线程。"); Console.ReadLine(); mre.Set(); Thread.Sleep(500); Console.WriteLine("按回车键,当ManualResetEvent有信号时,调用WaitOne()的线程不会阻塞。"); Console.ReadLine(); for (int i = 3; i <= 4; i++) { Thread t = new Thread(ThreadProc); t.Name = "Thread_" + i; t.Start(); } Thread.Sleep(500); Console.WriteLine("按回车键,调用Reset(),这样线程在调用WaitOne()时就会再次阻塞。"); Console.ReadLine(); mre.Reset(); // Start a thread that waits on the ManualResetEvent. Thread t5 = new Thread(ThreadProc); t5.Name = "Thread_5"; t5.Start(); Thread.Sleep(500); Console.WriteLine("按回车键,调用Set()并结束运行。"); Console.ReadLine(); mre.Set(); // 如在Visual Studio中运行这个例子,取消下面这行注释: //Console.ReadLine(); } private static void ThreadProc() { string name = Thread.CurrentThread.Name; Console.WriteLine(name + " 调用 mre.WaitOne()"); mre.WaitOne(); Console.WriteLine(name + " 结束"); } } }
3、ManualResetEventSlim的使用
参考文档:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.manualreseteventslim?view=net-6.0
new ManualResetEventSlim(false)
为创建在无信号状态。Wait()
方法会阻塞,调用Set()
方法设置为有信号状态,Wait()
方法不会阻塞,需要调用Reset()
方法变为无信号状态,Wait()
方法将会阻塞。new ManualResetEventSlim(true)
为创建在有信号状态。Wait()
方法不会阻塞,需要调用Reset()
方法变为无信号状态,Wait()
方法将会阻塞。
例如,
using System; using System.Threading; using System.Threading.Tasks; namespace ConsoleApplication { class Program { static void Main() { MRES_SetWaitReset(); MRES_SpinCountWaitHandle(); } // // ManualResetEventSlim construction // ManualResetEventSlim.Wait() // ManualResetEventSlim.Set() // ManualResetEventSlim.Reset() // ManualResetEventSlim.IsSet static void MRES_SetWaitReset() { ManualResetEventSlim mres1 = new ManualResetEventSlim(false); // 初始创建为无信号状态 ManualResetEventSlim mres2 = new ManualResetEventSlim(false); // 初始创建为无信号状态 ManualResetEventSlim mres3 = new ManualResetEventSlim(true); // 初始创建为有信号状态 // 启动一个操作mres3和mres2的异步任务 var observer = Task.Factory.StartNew(() => { mres1.Wait(); Console.WriteLine("observer mres1 信号"); Console.WriteLine("observer Reset mres3 信号"); mres3.Reset(); // mres3 应该无信号 Console.WriteLine("observer Set mres2"); mres2.Set(); }); Console.WriteLine("主线程: mres3.IsSet = {0} (应该为true)", mres3.IsSet); Console.WriteLine("主线程 Set mres1"); mres1.Set(); // 将会执行 observer Task mres2.Wait(); // observer Task完成Reset mres3,Set mres2 Console.WriteLine("主线程 mres2 有信号"); Console.WriteLine("主线程: mres3.IsSet = {0} (应该为false)", mres3.IsSet); // 用完ManualResetEventSlim时,Dispose()一个ManualResetEventSlim释放资源 observer.Wait(); // 确保observer执行完成 mres1.Dispose(); mres2.Dispose(); mres3.Dispose(); // 如在Visual Studio中运行这个例子,取消下面这行注释: Console.ReadLine(); } // : // ManualResetEventSlim construction w/ SpinCount // ManualResetEventSlim.WaitHandle static void MRES_SpinCountWaitHandle() { // 构造一个SpinCount为1000的ManualResetEventSlim ManualResetEventSlim mres1 = new ManualResetEventSlim(false, 1000); ManualResetEventSlim mres2 = new ManualResetEventSlim(false, 1000); Task bgTask = Task.Factory.StartNew(() => { // Just wait a little Thread.Sleep(100); // 给两个MRESes发信号 Console.WriteLine("mres1.Set() mres2.Set()"); mres1.Set(); mres2.Set(); }); //等待指定数组中的所有元素都收到信号。 WaitHandle.WaitAll(new WaitHandle[] { mres1.WaitHandle, mres2.WaitHandle }); Console.WriteLine("WaitHandle.WaitAll(mres1.WaitHandle, mres2.WaitHandle) completed."); // Clean up bgTask.Wait(); mres1.Dispose(); mres2.Dispose(); } } }