C#多线程Monitor类锁定资源Monitor.TryEnter()的用法。通过一个例子告诉你什么是死锁。和解决方案

本文通过一个具体的死锁示例,演示了如何使用Monitor.TryEnter方法避免线程无限期阻塞,并解释了其背后的原理。Monitor.TryEnter允许线程在指定时间内尝试获取锁,若超时未获取到锁,则返回false。
Monitor.TryEnter(lock1, TimeSpan.FromSeconds(5))

该方法是检查锁对象在指定时间内是否释放资源,如果释放则返回true否则false

下面是死锁的例子:(大家复制到自己VS中跑一下,我给的是完整控制台代码,打断点调试一下,结合着我的注释,写的很详细。应该会理解的。)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using static System.Console;
using static System.Threading.Thread;

namespace _9.Monitor类锁定资源
{
    class Program
    {
        static void Main(string[] args)
        {
            object lock1 = new object();
            object lock2 = new object();
            new Thread(()=>LockTooMuch(lock1,lock2)).Start();
            lock (lock2)
            {
                Thread.Sleep(1000);
                //监视器.TryEnter允许不被卡住,在指定的超时时间过后返回false
                WriteLine("Monitor.TryEnter allows not to get stuck,returning false after a specified timeout is elapsed");
                //判断当前锁的对象是否释放,超时就代表还未释放。
                if (Monitor.TryEnter(lock1, TimeSpan.FromSeconds(5)))
                {
                    //成功获取受保护的资源
                    WriteLine("cquired a protected resoured resource succesfully");
                }
                else
                {
                    //获取资源超时
                    WriteLine("Timeout acquiring a resource!");
                }
            }
            new Thread(()=> { LockTooMuch(lock1, lock2); }).Start();

            WriteLine("---------------------------");
            lock (lock2)//-----------------此时主线程已经锁死lock2
            {   
                //这是一个死锁
                WriteLine("This will be a deadlock");
                Sleep(1000);
                lock (lock1)   //=============主线程等待子线程释放lock1
                {
                    //已成功获取受保护的资源
                    WriteLine("Acquired a protected resource succesfully");
                }
            }
        }
        //上下俩段注释对比发现   主线程的lock2锁死导致子线程无法释放lock1  因为子线程在等待着主线程释放lock2  此时就造成了死锁。

        static void LockTooMuch(object lock1 ,object lock2)
        {
            lock (lock1)//=============子线程已经锁死lock1
            {
                Sleep(1000);
                lock (lock2);  //----------------子线程等待着主线程的lock2释放   子线程处于等待状态
            }
        }

    }
}

大概解释一下:(一定要自己跑一遍,打断点调试。)
先看 看 LockTooMuch 方法。 在 该 方法 中 我们 先 锁定 了 第一个 对象, 等待 一秒 后 锁定 了 第二个 对象。 然后 在另 一个 线程 中 启动 该 方法。 最后 尝试 在 主 线程 中 先后 锁定 第二个 和 第一个 对象。 如果 像 该 示例 的 第二 部分 一样 使用 lock 关键字, 将会 造成 死锁。 第一个 线程 保持 对 lock1 对象 的 锁定, 等待 直到 lock2 对象 被 释放。 主线 程 保持 对 lock2 对象 的 锁定 并 等待 直到 lock1 对象 被 释放, 但 lock1 对象 永远 不会 被 释放。 实际上 lock 关键字 是 Monitor 类 用 例 的 一个 语法 糖。 如果 我们 分解 使用 了 lock 关键字 的 代码, 将会 看到 它 如下 面 代码 片段 所示:

 bool acquiredLock = false;
            try
            {
                ////访问受锁保护的资源的代码
                Monitor.Enter(lockObject, ref acquiredLock);
                //Code that accesses resources that are protected by the lock
            }
            finally
            {
                if (acquiredLock)
                {
                    Monitor.Exit(lockObject);
                }
            }

因此, 我们 可以 直接 使用 Monitor 类。 其 拥有 TryEnter 方法, 该 方法 接受 一个 超时 参数。 如果 在 我们 能够 获取 被 lock 保护 的 资源 之前, 超时 参数 过期, 则 该 方法 会 返回 false。

<think>我们已经了解了用户的问题:询问C#Monitor.TryEnter(hDisplay.m_hDev.m_mutexImage, 2000, ref lockTaken)的用法。 根据引用内容,我们得知: 1. Monitor.TryEnter方法可以避免死锁,因为它允许设置超时时间。 2. 该方法尝试在指定的超时时间内获取一个对象的互斥锁。 3. 如果成功获取锁,则返回true,并将lockTaken参数设置为true;如果在超时时间内未能获取锁,则返回false,lockTaken为false。 具体到这个语句: hDisplay.m_hDev.m_mutexImage: 这是一个对象,作为锁的目标。 2000: 超时时间,单位是毫秒(即2秒)。 ref lockTaken: 一个bool型的引用参数,用于指示是否成功获取了锁。 使用步骤: 1. 在调用TryEnter之前,通常需要先声明一个bool变量(例如lockTaken),并初始化为false。 2. 调用Monitor.TryEnter,传入锁对象、超时时间这个bool变量的引用。 3. 检查lockTaken的值,如果为true,则表示在超时时间内成功获取了锁,然后可以安全地访问受保护的资源。 4. 在访问完资源后,必须在finally块中调用Monitor.Exit来释放锁(但前提是成功获取了锁)。 注意:引用[4]中提到一个避坑点,即锁对象必须是一个引用型(如object),不能是值型(如bool)。因为lockMonitor要求锁对象是引用型,否则会出现不可预期的行为。 因此,我们可以这样写代码: ```csharp bool lockTaken = false; try { // 尝试在2000毫秒内获取锁 Monitor.TryEnter(hDisplay.m_hDev.m_mutexImage, 2000, ref lockTaken); if (lockTaken) { // 成功获取锁,在这里安全地访问共享资源 // ... 你的代码 ... } else { // 超时,未能获取锁,可以处理超时情况(比如重试、记录日志或抛出异常等) Console.WriteLine("获取锁超时!"); } } finally { // 如果成功获取了锁,则释放它 if (lockTaken) { Monitor.Exit(hDisplay.m_hDev.m_mutexImage); } } ``` 注意:这里使用try-finally是为了确保在获取锁之后,无论是否发生异常,都能释放锁,避免死锁。 引用说明: 根据引用[1][2],我们使用Monitor.TryEnter来避免死锁,因为它允许设置超时。 引用[4]提醒我们锁对象必须是引用型(如object实例),不能是值型(如bool)。 如果用户提供的hDisplay.m_hDev.m_mutexImage是值型,则会导致错误,必须修正为引用型。 因此,回答用户的问题时,我们需要解释该语句的每个参数,并给出正确使用示例,同时提醒锁对象的型要求。 </think>### C# 中 `Monitor.TryEnter(hDisplay.m_hDev.m_mutexImage, 2000, ref lockTaken)` 的用法详解 #### 1. **核心功能** - **避免死锁的关键机制**:该方法尝试在指定超时时间内获取对象的互斥锁,避免线程无限期阻塞[^1][^2]。 - **参数解析**: - `hDisplay.m_hDev.m_mutexImage`:需加锁的对象(必须是引用型,否则会引发异常[^4])。 - `2000`:超时时间(毫秒),此处为 2 秒。 - `ref lockTaken`:输出参数,指示是否成功获取锁(`bool` 型)。 #### 2. **标准使用模式** ```csharp bool lockTaken = false; // 必须显式初始化 try { // 尝试在2秒内获取锁 Monitor.TryEnter(hDisplay.m_hDev.m_mutexImage, 2000, ref lockTaken); if (lockTaken) { // 临界区:操作共享资源 // 示例:访问 hDisplay 的设备图像数据 Console.WriteLine("成功获取锁,操作资源..."); } else { // 超时处理 Console.WriteLine("获取锁超时!"); } } finally { // 若成功获取锁则必须释放 if (lockTaken) { Monitor.Exit(hDisplay.m_hDev.m_mutexImage); } } ``` #### 3. **关键注意事项** 1. **锁对象要求**: - 锁对象必须是引用型(如 `object`),值型(如 `bool`)会导致运行时错误[^4]。 - 推荐使用专用锁对象:`private readonly object _lockObj = new object();` 2. **超时处理的意义**: - 当多个线程竞争资源时,超时机制可防止系统僵死(例如 UI 线程不冻结)[^1][^2]。 - 超时后需设计回退策略(如重试、日志告警或资源降级)。 3. **`lockTaken` 的必要性**: - 明确区分成功获取锁超时场景,避免错误调用 `Monitor.Exit()`[^2]。 - CLR 内部通过 `SyncBlock` 机制跟踪锁状态(参考 `clr/src/vm/syncblk.h`[^3])。 #### 4. **适用场景** - **设备驱动交互**:如相机/传感器等硬件资源访问需严格时序控制。 - **高并发服务**:数据库连接池、网络通信等可能发生资源争用的场景。 - **实时系统**:要求操作必须在确定时间内完成。 #### 5. **常见错误规避** ```csharp // ❌ 错误示例:值型作为锁对象(编译通过但运行时崩溃) static bool lockObj = false; // ✅ 正确做法:使用引用型 static readonly object lockObj = new object(); ``` > 引用[^4]的实验证明:值型锁对象会破坏锁机制,导致线程同步失效。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值