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();
}
}
}