Monitor.TryEnter未按预期生效,请求排查代码错误原因
问题描述
我尝试使用Monitor.TryEnter()避免临界代码区被多次访问,但锁并未生效。我编写了一段控制台代码片段复现该问题:
class Program { static object _lockObject = new object(); static void Main(string[] args) { if (Monitor.TryEnter(_lockObject, 0)) { try { if (Monitor.TryEnter(_lockObject, 0)) { // I should not be allowed to get here. Console.WriteLine("Seems it didn't work."); } } finally { Monitor.Exit(_lockObject); } } Console.ReadLine(); } }
我原本预期锁定对象后无法再次获取锁,但显然锁被忽略。在我的GUI程序中也存在相同问题:操作完成前重复点击按钮无法被Monitor阻止。请问我哪里操作错误?
问题原因与解决办法
这是因为Monitor是可重入锁——同一个线程可以多次获取同一把锁,每次获取都需要对应一次Exit调用。你的代码里,主线程第一次调用Monitor.TryEnter成功获取锁后,再次调用Monitor.TryEnter会直接成功,因为是同一个线程。
控制台代码的修正方案
如果要模拟跨线程的锁竞争场景,需要启动新线程来尝试获取锁,而非在同一线程内重复调用:
class Program { static object _lockObject = new object(); static void Main(string[] args) { if (Monitor.TryEnter(_lockObject, 0)) { try { Console.WriteLine("Main thread acquired lock."); // 启动新线程尝试获取锁 var workerThread = new Thread(() => { if (Monitor.TryEnter(_lockObject, 1000)) { try { Console.WriteLine("Worker thread acquired lock."); } finally { Monitor.Exit(_lockObject); } } else { Console.WriteLine("Worker thread failed to acquire lock."); } }); workerThread.Start(); workerThread.Join(); } finally { Monitor.Exit(_lockObject); } } Console.ReadLine(); } }
运行这段代码,新线程会无法获取锁,符合预期的锁竞争行为。
GUI按钮重复点击的解决思路
GUI程序中,重复点击按钮时,事件处理代码通常在同一个UI线程执行,因此Monitor.TryEnter会直接成功,无法阻止重复触发。可以采用两种方案解决:
- 直接禁用按钮:点击后立即禁用按钮,待操作完成后再启用,从源头阻止重复点击。
- 使用原子操作标记:用
Interlocked类操作一个标记变量,判断是否正在执行操作,避开Monitor的重入特性:
private int _isProcessing = 0; private void Button_Click(object sender, EventArgs e) { // 原子操作判断是否正在处理,0表示未处理,1表示正在处理 if (Interlocked.CompareExchange(ref _isProcessing, 1, 0) == 0) { try { // 执行临界操作,耗时操作建议放后台线程避免阻塞UI Task.Run(() => { // 模拟耗时任务 Thread.Sleep(2000); // 回到UI线程更新状态 this.Invoke(() => { // 这里可以更新UI内容 }); }).ContinueWith(t => { // 操作完成后重置标记 Interlocked.Exchange(ref _isProcessing, 0); }); } catch { // 异常情况下也要重置标记 Interlocked.Exchange(ref _isProcessing, 0); throw; } } }
这样就能有效阻止同一UI线程下的重复点击触发操作。
内容的提问来源于stack exchange,提问作者Thern
相关产品推荐
相关产品推荐

