在.net中,还可以使用Monitor实现线程并发同步。Monitor类是纯托管且完全可移植,并且可能会在操作系统资源需求方面更加高效。
Monitor的锁对象尽可能使用引用对象,如果是字符串或值对象,会出现引发SynchronizationLockException异常。
其实我们日常用的lock锁同步,其原理就是基于Monitor的。
即:
public static readonly object locker=new object(); lock(locker){ //to do something }
和以下代码是一样的意思:
public static readonly object locker=new object(); try{ Monitor.Enter(locker); //to do something }finally{ Monitor.Exit(locker); }
以下是基于测试的代码:
protected static readonly object _locker = new object();
[HttpGet("[controller]/api/[action]")] public IActionResult Test() { int threadId = Thread.CurrentThread.ManagedThreadId; ResponseModel rc = new ResponseModel(0, $"{threadId} 初始化"); try { //独占资源锁 Monitor.Enter(_locker); int count = RedisHelper.GetAsync(RTestKey).Result.ToInt32(); if (count > 0) { if (RedisHelper.SetAsync(RTestKey, "-1").Result) rc.SetMessage($"{threadId} 设置成功!"); } else rc.SetMessage($"{threadId} 已被设置,不能再次设置!"); } catch (Exception ex) { _log.Error(ex); } finally { //释放锁,让其他线程获取锁并进入该方法 Monitor.Exit(_locker); } return Json(rc); }
接下来通过测试程序连续发送8次请求,看看结果:
通过以上操作,证明可以使用Monitor实现多线程的并发同步操作。
如果在执行了Monitor.Enter(locker)之后,需要释放锁对象并阻塞当前线程,让其他线程获取锁并执行的话,可以使用Monitor.Wait()方法。Monitor.Wait()方法的作用就是释放当前线程持有的锁,让当前线程进入等待队列,让其他某个线程获取锁对象并进入就绪状态并执行。其他线程执行Monitor.Pulse或PulseAll方法之后,当前等待的线程就可以继续获取锁并进入就绪队列去执行。
Monitor的Wait、Pulse和PulseAll方法必须在Enter和Exit方法之间调用。
如下测试代码:
static void Func1() { Monitor.Enter(_locker); for(int i = 1; i < 5; i++) { Console.Write($"=>Func1【{i}】"); Monitor.Pulse(_locker); Monitor.Wait(_locker); } Monitor.Exit(_locker); } static void Func2() { Monitor.Enter(_locker); for (int i = 1; i < 5; i++) { Monitor.Wait(_locker); Console.Write($"=>Func2【{i}】"); Monitor.Pulse(_locker); } Monitor.Exit(_locker); } static void Func4() { List<Task> list = new List<Task>{ Task.Factory.StartNew(Func1), Task.Factory.StartNew(Func2) }; Task.WaitAll(list.ToArray()); } public static readonly object _locker = new object(); static void Main(string[] args) { Func4(); }
这段代码的目的是Fun1函数执行一次后,Fun2函数再执行一次,依次循环。让我们看看执行的结果:
由此看来,达到了本次测试的目的。