ManualResetEvent,即手动重置事件,通过信号量来判别当前线程是否应该阻塞或继续执行。使用方式与ManualResetEventSlim差不多,ManualResetEventSlim只是针对ManualResetEvent轻量化的使用。

当执行ManualResetEvent.Set()时,ManualResetEvent会获得信号,并释放所有正在阻塞的线程

当执行ManualResetEvent拥有信号时,执行WaitOne()时会立即释放当前线程,即当前线程会继续执行;若ManualResetEvent没有拥有信号时,执行WaitOne()时会被立即阻塞。

 

一、在控制台应用程序中测试

请看如下代码:

private static ManualResetEvent mre = new ManualResetEvent(true);
static void Main(string[] args)
{
            //释放信号量
            mre.Reset();
            for (int i = 0; i < 3; i++)
            {
                Thread t = new Thread(ManualSubFuncTest);
                t.Name = $"Thread.{i}";
                t.Start();
            }
            Thread.SpinWait(10);
            Console.WriteLine("all threads have started,please waiting for 3 seconds...");
            Thread.Sleep(3000);
            //获取信号量
            mre.Set();

            Console.ReadKey();
}

static void ManualSubFuncTest()
{
            string name = Thread.CurrentThread.Name;
            Console.WriteLine($"{name} starts and calling waitone() method " );
            //如果mre不拥有信号量,则等待,否则继续执行
            mre.WaitOne();
            Console.WriteLine($"{name} ends");
}

结果如下图所示:

测试结果和预期一样。 

 

二、在webapi项目中测试

代码如下:

       [HttpGet("[controller]/v1/api/[action]")]
        public IActionResult Test() {
            return Json(SynchronizationTest());
        } 

        protected static int Counter = 1;//1:空闲 0:非空闲     
        protected static ManualResetEvent Mre=new ManualResetEvent(false);
        public ResponseModel SynchronizationTest() {
            ResponseModel rc = new ResponseModel(0, "初始化");
           
            try
            {
                Mre.Reset();
                //如果其他线程正在操作,则等待,5秒后超时
                if (Interlocked.CompareExchange(ref Counter, 0, 1) == 0)
                   Mre.WaitOne(3000);

                
                int count = RedisHelper.Get(GoodsNumberKey).ToInt32();
                if (count > 0) {
                    RedisHelper.Set(GoodsNumberKey, "-1");
                    rc.SetMessage("重置成功!");
                }
                else rc.SetMessage("已被重置,本次重置无效");
            }
            catch (Exception ex) {
                _log.Error(ex);
            }
            finally {
                //转为空闲状态
                Interlocked.Exchange(ref Counter, 1);
                //设置信号量,让上面的 Mre.Wait(3000);取消等待,继续执行代码
                Mre.Set();
            }

            return rc;
        }

接下来,我们连续发送8次请求,看看结果如何:

由结果可以看到,多个线程中,只有一个线程操作成功,起到了并发同步的目的。

注意:信号量事件(ManualResetEvent)对象要用同一个的WaitOne、Reset和Set配合才会实现并发同步的效果。

如果是轻量化的线程间同步操作,建议用ManualResetEventSlim。其效果和ManualResetEvent是一样的,可以去看看我的《.net core WebApi Interlocked配合ManualResetEventSlim实现并发同步》这篇随笔。

 

 posted on 2018-09-29 11:53  F风  阅读(988)  评论(0编辑  收藏  举报