You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 16:51:32