Monitor.TryEnter始终返回true未生效,异步场景下如何解决?
关于Monitor.TryEnter在异步方法中失效的问题
测试代码
public class Test { public async Task Lock(object _lock) { if (!Monitor.TryEnter(_lock)) { Console.WriteLine("locked"); return; } Console.WriteLine("entered"); await Task.Delay(99999); Monitor.Exit(_lock); Console.WriteLine("exited"); } } static async Task Main() { var _lock= new object(); var test = new Test(); do { test.Lock(_lock); Thread.Sleep(999); } while (true); }
预期输出
entered locked locked ... exited entered locked locked ... exited
实际输出
entered entered entered ... exited
问题原因
核心问题是Monitor是基于线程的同步原语,它的锁与持有锁的线程绑定。异步方法执行到await Task.Delay(...)时,当前线程会被释放,后续代码会在任意线程上恢复执行,这就导致:
- 第一次调用
Monitor.TryEnter成功获取锁,此时锁与执行这段代码的线程绑定; - await后线程被释放,后续的
Monitor.Exit会在另一个线程执行,但Monitor只认最初持有锁的线程; - Main方法中调用
test.Lock(_lock)时没有await,每次调用都是在Main线程执行Monitor.TryEnter——而Main线程从来没持有过这个锁,所以每次都能成功获取,自然会连续输出"entered"。
解决方案
方案1:用SemaphoreSlim实现异步锁(推荐)
SemaphoreSlim支持异步等待,是专门为异步场景设计的同步原语,完全适配你的需求:
public class Test { public async Task Lock(SemaphoreSlim semaphore) { // WaitAsync(0)表示立即返回,等价于Monitor.TryEnter的非阻塞行为 if (!await semaphore.WaitAsync(0)) { Console.WriteLine("locked"); return; } Console.WriteLine("entered"); await Task.Delay(99999); semaphore.Release(); Console.WriteLine("exited"); } } static async Task Main() { // 初始计数1、最大计数1,等价于互斥锁 var semaphore = new SemaphoreSlim(1, 1); var test = new Test(); do { // 这里必须await,避免堆积大量未完成的任务 await test.Lock(semaphore); Thread.Sleep(999); } while (true); }
方案2:避免在持有Monitor锁时执行await(仅适合特定场景)
如果一定要用Monitor,必须保证持有锁的期间不释放线程,也就是把await移出锁的范围,但这种方式会让异步操作脱离锁的保护,不符合你的预期逻辑,仅作参考:
public async Task Lock(object _lock) { bool lockTaken = false; try { Monitor.TryEnter(_lock, ref lockTaken); if (!lockTaken) { Console.WriteLine("locked"); return; } Console.WriteLine("entered"); // 锁只保护到这里,异步操作在锁外执行 } finally { if (lockTaken) { Monitor.Exit(_lock); } } await Task.Delay(99999); Console.WriteLine("exited"); }
内容的提问来源于stack exchange,提问作者Ian
相关产品推荐
相关产品推荐

