C#线程调度AutoResetEvent和ManualResetEvent区别
一、ManualResetEvent的使用
ManualReset是一种同步原语,它可以用于控制多个线程的执行顺序。ManualReset通常用于线程之间的通信,例如一个线程需要等待另一个线程完成某个操作后才能继续执行。
ManualReset的工作原理很简单,它维护一个状态变量,当状态为true时,所有等待的线程都可以继续执行;当状态为false时,所有等待的线程都会被阻塞,直到状态变为true。
使用ManualReset时,一个线程可以调用ManualReset的Set方法来设置状态为true,这将会释放所有等待的线程。另一个线程则可以调用ManualReset的Reset方法来将状态设置为false,这将会阻塞所有等待的线程。
一般情况下,ManualReset常常与WaitHandle、AutoResetEvent、Semaphore等同步对象一起使用,来控制多个线程之间的通信和协作。
我们写一段测试代码:
internal class Program { static void Main(string[] args) { ManualResetEvent manualResetEvent = new ManualResetEvent(false); for (int i = 0; i < 5; i++) { int k = i; Task.Run(() => { Console.WriteLine($"第{k+1}个线程开始执行,Id 为:{Task.CurrentId}"); manualResetEvent.WaitOne(); Console.WriteLine($"第{k+1}个线程继续执行,Id 为:{Task.CurrentId}..."); }); } Stopwatch sw = Stopwatch.StartNew(); sw.Start(); while ( sw.ElapsedMilliseconds <10000 ) { Thread.Sleep(1000); Console.WriteLine($"----{(int)sw.Elapsed.TotalSeconds} S ----"); } Console.WriteLine("已经等待了10s,发送信号量..."); manualResetEvent.Set(); Console.ReadLine(); } }
执行结果:
实现一个最多等待线程执行5s的例子
internal class Program { static void Main(string[] args) { Console.WriteLine("main thread begin..."); ManualResetEvent manualResetEvent = new ManualResetEvent(false); CancellationTokenSource cts = new CancellationTokenSource(); CancellationToken token = cts.Token; Task.Run(() => { Test(token); }, token); Console.WriteLine("main thread wait..."); manualResetEvent.WaitOne(5000); cts.Cancel(); Console.WriteLine("main thread continue..."); Console.ReadLine(); } public static void Test(CancellationToken cancellationToken) { Console.WriteLine("sub thread begin---"); Stopwatch sw = Stopwatch.StartNew(); sw.Start(); while (!cancellationToken.IsCancellationRequested) { Console.WriteLine($"test method is running {(int)sw.Elapsed.TotalSeconds} S,-----"); Thread.Sleep(1000); } } }
结果:
二、AutoResetEvent的使用
一个简单例子
internal class Program { static void Main(string[] args) { AutoResetEvent manualResetEvent = new AutoResetEvent(false); for (int i = 0; i < 5; i++) { int k = i; Task.Run(() => { Console.WriteLine($"第{k+1}个线程开始执行,Id 为:{Task.CurrentId}"); manualResetEvent.WaitOne(); Console.WriteLine($"第{k+1}个线程继续执行,Id 为:{Task.CurrentId}..."); }); } Stopwatch sw = Stopwatch.StartNew(); sw.Start(); while ( sw.ElapsedMilliseconds <10000 ) { Thread.Sleep(1000); Console.WriteLine($"----{(int)sw.Elapsed.TotalSeconds} S ----"); } Console.WriteLine("已经等待了10s,发送信号量..."); manualResetEvent.Set(); manualResetEvent.Set(); Console.ReadLine(); } }
结果:
由此可见,AutoResetEvent一次只能解锁一个线程。
三、二者对比
共同点:
均继承 EventWaitHandle 接口,因此,均具有以下功能:
Reset() //红灯
Set() //绿灯
WaitOne() // 等待信号
本质都是阻塞信号模型,就像windows模型
whtle(true)
{
Sleep(1);
sing=true;
}
不同点:
AutoResetEvent 收到 Set 后 , 一次只能执行一个线程,其它线程继续 WaitOne 。
ManualResetEvent 收到 Set 后,所有处理 WaitOne 状态线程均继续执行。
msdn 提到(如果没有线程 处于WaitOne() 状态,而调用 Set , AutoResetEvent 将保持Set 状态):
调用Set信号AutoResetEvent释放等待线程。 AutoResetEvent 将保持终止状态直到一个等待线程释放,并自动返回到非信号状态。 如果没有线程处于等待状态,状态将无限期地保持已发出信号。
因此通常WatiOne 之前,先 Reset() 一下,清除Set 信号
需要注意的是(两个 Set 调用之间时间较短,第二个 Set 信号可能会丢失,因此连续 Set 调用,中间需要 Sleep 一定时间):
不能保证的每个调用Set方法将释放一个线程。 如果两次调用太靠近在一起,以便第二次调用前释放线程发生,只有一个线程被释放。
就像第二次调用未发生。 此外,如果Set时没有等待的线程调用和AutoResetEvent已终止,则调用不起作用。
原理上
AutoResetEvent.Set() = ManualResetEvent.Set() + ManualResetEvent.Reset();
实际使用过程中,有差别的,如下示例:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace testManualResetEvent { class Program { static object objManualResetEvent = new object(); static System.Threading.ManualResetEvent manu = new System.Threading.ManualResetEvent(false); //static System.Threading.AutoResetEvent manu = new System.Threading.AutoResetEvent(false); static void Main(string[] args) { for (int i = 0; i < 10; i++) { System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(() => { Product(); })); t.Start(); } manu.Set(); manu.Reset(); Console.ReadKey(); } static void Product() { manu.WaitOne(10000); Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId); } } }
实际执行结果 , 在 执行 set 后 reset 前 ,有多少个线程唤起执行,无法预料:
需要加锁 ,确保一次通过一个线程:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace testManualResetEvent { class Program { static object objManualResetEvent = new object(); static System.Threading.ManualResetEvent manu = new System.Threading.ManualResetEvent(false); //static System.Threading.AutoResetEvent manu = new System.Threading.AutoResetEvent(false); static void Main(string[] args) { for (int i = 0; i < 10; i++) { System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(() => { Product(); })); t.Start(); } manu.Set(); //System.Threading.Thread.Sleep(100); //连续 set 需要 sleep //manu.Set(); //manu.Reset(); //System.Threading.Thread.Sleep(100); //manu.Set(); //manu.Reset(); Console.ReadKey(); } static void Product() { lock (objManualResetEvent) { manu.WaitOne(10000);
manu.Reset(); Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId); } } } }