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); } } } }
posted @ 2021-10-16 15:20  卖雨伞的小男孩  阅读(178)  评论(0编辑  收藏  举报