.Net中信号量的使用

信号量的使用类,在System.Threading下,主要有三种,Semaphore,ManualResetEvent,CountdownEvent,下面我一一介绍这三个类的作用:

一、Semaphore类

Semaphore的主要作用是控制并发,初始设置时,需要设定初始入口数,和最大并发入口数。这个型号量就像是一种排队,就是说,N个线程通过WaitOne进队,又通过Realse出队这么一个概念。

最常用的地方,无锁线程同步,Http请求并发控制等等。

写一个简单的线程锁的例子:

Semaphore lock=new Semaphore(1,1);//意思是设置最大并发只有1,且初始入口只有1的信号量

try

{

lock.WaitOne();

//TODO:写需要线程同步代码的区域

}

finally

{

lock.Realse();//通过finally出锁,这样避免在return或者出现异常的时候漏掉Realse,导致线程卡死

}

二、ManualResetEvent 类

ManualResetEvent是单信号控制的,WaitOne等待信号,Set去发送信号。

new ManualResetEvent(false)//表示当前没有信号

new ManualResetEvent(true)//表示当前有信号

这个信号量场景应用非常丰富,下面举一下例子:

控制循环线程:

Thread thread=new Thread(Run);

ManualResetEvent close=new ManualResetEvent(false);

private void Run()

{

while(!close.WaitOne(20))

{

       //因为close在没有关闭之前没有信号,所以WaitOne(20),就是在等待20毫秒之后返回false

       //那么这样写就可以达到,每隔20毫秒运行一下while里面的代码的效果

}

}

public void Close()

{

     close.Set();//当要关闭的时候,设置close为有信号状态

     thread.Join();//等待线程关闭,因为close已设置成有信号状态,所以必定跳出while循环,那么Join肯定可以完全结束线程

}

网页利用Ajax等待事件触发:

我一直觉得这是一个很棒的想法,网页中在未知的情况下等待某个用户操作完成是一个很头疼的事情,一般要通过轮询解决,比如支付,现在可以利用信号量来完成这个事情。

最大的利用点,就是Ajax是异步请求,这就相当于创建了一个线程,可以在完成支付的时候触发一个Http请求,然后服务器对这个Http请求处理是,找到对应的信号量,先将数据存入内存,然后触发它,

信号量被触发之后,WaitOne就结束了,然后拿到内存中的数据并返回。

可以这么做,下面我简单的写一下代码,实际也证明可行:

    public class TimerHandler
    {
        public Guid Id { get; set; }

        /// <summary>
        /// 超时时间
        /// </summary>
        public int TimeOut { get; set; }

        /// <summary>
        /// 是否已被使用
        /// </summary>
        public bool IsUsed { get; set; }

        /// <summary>
        /// 信号量是否已经被释放
        /// </summary>
        public bool IsDisposed { get; set; }

        /// <summary>
        /// 数据
        /// </summary>
        public string MemoryData { get; set; }

        public ManualResetEvent WaitHandle { get; set; }
    }

    public class TimerManager
    {
        /// <summary>
        /// 单例
        /// </summary>
        private static readonly TimerManager _manager = new TimerManager();

        /// <summary>
        /// 线程同步控制信号量
        /// </summary>
        private readonly Semaphore _lock = new Semaphore(1, 1);

        /// <summary>
        /// 储存信号量的一个字典结构
        /// </summary>
        private Dictionary<Guid, TimerHandler> _timerDic = new Dictionary<Guid, TimerHandler>();

        /// <summary>
        /// 当前实例
        /// </summary>
        public static TimerManager Current { get { return _manager; } }

        /// <summary>
        /// 创建触发事件的句柄,也就是一个Id值
        /// </summary>
        /// <param name="timeOut">设置等待的超时时间</param>
        /// <returns></returns>
        public Guid CreateEvent(int timeOut)
        {
            try
            {
                if (timeOut <0 || timeOut > 3600000)
                {
                    timeOut = 3600000;
                }
                _lock.WaitOne();
                TimerHandler handler = new TimerHandler();
                handler.Id = Guid.NewGuid();
                handler.TimeOut = timeOut;
                handler.WaitHandle = new ManualResetEvent(false);
                if (!_timerDic.ContainsKey(handler.Id))
                {
                    _timerDic.Add(handler.Id, handler);
                }
                return handler.Id;
            }
            finally
            {
                _lock.Release();
            }
        }

        /// <summary>
        /// 等待事件触发
        /// </summary>
        /// <param name="eventId"></param>
        /// <param name="res"></param>
        /// <returns></returns>
        public bool WaitEvent(Guid eventId,out string res)
        {
            res = null;
            TimerHandler handle = null;
            try
            {
                _lock.WaitOne();
                handle = _timerDic[eventId];
            }
            finally
            {
                _lock.Release();
            }
            if (handle != null)
            {
                lock (handle)
                {
                    handle.IsUsed = true;
                }
                var r=handle.WaitHandle.WaitOne(handle.TimeOut);
                try
                {
                    _lock.WaitOne();
                    _timerDic.Remove(handle.Id);
                }
                finally
                {
                    _lock.Release();
                }
                lock (handle)
                {
                    if (!handle.IsDisposed)
                    {
                        handle.IsDisposed = true;
                        handle.WaitHandle.Close();
                        handle.WaitHandle.Dispose();
                    }
                }
                if (r)
                {
                    res = handle.MemoryData;
                }
                return r;
            }
            return false;
        }

        /// <summary>
        /// 触发事件
        /// </summary>
        /// <param name="eventId"></param>
        /// <param name="json"></param>
        public void RaiseEvent(Guid eventId,string json)
        {
            TimerHandler handle = null;
            try
            {
                _lock.WaitOne();
                handle = _timerDic[eventId];
            }
            finally
            {
                _lock.Release();
            }
            lock (handle)
            {
                if (!handle.IsDisposed)
                {
                    //如果等待超时了IsDisposed为true,
                    //此时信号量已经释放了,不用Set,否则会报错
                    handle.MemoryData = json;
                    handle.WaitHandle.Set();
                }
            }
        }
    }

然后设置Http的三个接口,分别调用,CreateEvent,WaitEvent,RaiseEvent(省了点事,以下就简要写了,实际用到的代码是有,但不做展示):

js上这么写:

   function GetCallBackUrl(eventId) {
//返回能触发RaiseEvent请求的链接
return "http://***/RaiseEvent?id=" + eventId; } //event_fun 获取回调函数Id之后的处理 //callbackFunction 获得回调数据之后的处理,可能存在超时 //timeout 超时时间 function BeginAsCallBack(event_fun,callbackFunction,timeout) {
//先调用CreateEvent获取EventId $.post(
"http://***/CreateEvent", timeout, function (res) { eventId = res.EventId;
//然后调用WaitEvent等待请求,一直等待,因为是异步,所以依然可以执行下面的js代码
//先用ajax执行等待 $.post("http://***/WaitEvent", JSON.stringify({ EventId: eventId }), callbackFunction, "json"); //然后触发支付 event_fun(eventId); }, "json"); } //执行支付 function DoPay() { BeginAsCallBack(function (eventId) { var req = { CallBackUrl:GetCallBackUrl(eventId) //触发RaiseEvent请求的Url
//支付请求参数 }; //请求支付 $.post("http://***/Pay", JSON.stringify(req), function (result, status, xhr) { // }, "json"); }, function (res_callBack) { if (res_callBack.IsTimeOut) { alert("回调超时"); } else { var data=res_callBack.Data;//回调数据 } }, 20000); }

这样就能达到支付完成之后,同时原网页上会有提示信息的效果。

三、CountdownEvent类

CountdownEvent类,在值减少到0的时候,发出信号,主要的用途在于触发了多个线程,这些线程是未知数量的,需要等待这些线程同时完成。

通常用于并发遍历,并发搜索等场景,方法简单,消耗内存较少。

posted on 2019-12-19 11:52  Marks周  阅读(389)  评论(0)    收藏  举报

导航