在开发多线程的应用程序时,我们会大量用到 lock (...) {} 块。如果 lock 的对象比较多,非常容易发生死锁。死锁的发生很难预料,而且一旦发生在界面线程上,界面就不再刷新响和应用户输入;如果发生在后台线程,后台线程也就阻塞不工作了,死锁必然会导致应用程序不可用。在.NET里发生死锁的原因是什么?

以 C# 为例,通常 lock 语句是被转化为对一个资源的无限长时间的等待,所以一旦资源被占用而又永不释放,那么必然死锁。

那么如何规避的危害呢?应用程序应该避免 lock(obj) 块,推荐使用 Monitor.TryEnter(obj, millisecondsTimeout) 代替,二者的第一个参数意义相同,而后者还可以设置等待超时时间,一旦在限定的时间内无法获得锁,那么 TryEnter 就会返回  false。这样就不会造成死锁,无法获得资源,业务程序可以采取重试或抛异常的方式进行善后处理。

Monitor.TryEnter 和 Monitor.Exit 必须成对出现,为了简化代码,可以用一个实现IDisposeable的类来封装这个过程:

/// <summary>
    /// 会自动释放的锁,可设置等待超时
    /// </summary>
    public class Lock : IDisposable
    {
        /// <summary>
        /// 默认超时设置
        /// </summary>
        public static int DefaultMillisecondsTimeout = 15000; // 15S
        private object _obj;

/// <summary>
        /// 构造 
        /// </summary>
        /// <param name="obj">想要锁住的对象</param>
        public Lock(object obj)
        {
            TryGet(obj, DefaultMillisecondsTimeout, true);
        }

/// <summary>
        /// 构造
        /// </summary>
        /// <param name="obj">想要锁住的对象</param>
        /// <param name="millisecondsTimeout">超时设置</param>
        public Lock(object obj, int millisecondsTimeout)
        {
            TryGet(obj, millisecondsTimeout, true);
        }

/// <summary>
        /// 构造
        /// </summary>
        /// <param name="obj">想要锁住的对象</param>
        /// <param name="millisecondsTimeout">超时设置</param>
        /// <param name="throwTimeoutException">是否抛出超时异常</param>
        public Lock(object obj, int millisecondsTimeout, bool throwTimeoutException)
        {
            TryGet(obj, millisecondsTimeout, throwTimeoutException);
        }

private void TryGet(object obj, int millisecondsTimeout, bool throwTimeoutException)
        {
            if (Monitor.TryEnter(obj, millisecondsTimeout))
            {
                _obj = obj;
            }
            else
            {
                if (throwTimeoutException)
                {
                    throw new TimeoutException();
                }
            }
        }

/// <summary>
        /// 销毁,并释放锁
        /// </summary>
        public void Dispose()
        {
            if (_obj != null)
            {
                Monitor.Exit(_obj);
            }
        }

/// <summary>
        /// 获取在获取锁时是否发生等待超时
        /// </summary>
        public bool IsTimeout
        {
            get
            {
                return _obj == null;
            }
        }
    }

调用例子:

using (Lock l = new Lock(obj, 10000))
{
    .... 
}

这样在代码离开 using 块后,会自动执行 Monitor.Exit释放锁。

是不是很棒? :)

利用 Monitor.TryEnter 来规避 .NET 线程死锁的源代码的更多相关文章

  1. 《C#多线程编程实战》1.11 Monitor.TryEnter()避免死锁

    这章的内容是真的有意思 特别是代码. 先贴上代码: class Program { static void Main(string[] args) { object lock1 = new objec ...

  2. 线程系列08,实现线程锁的各种方式,使用lock,Montor,Mutex,Semaphore以及线程死锁

    当涉及到多线程共享数据,需要数据同步的时候,就可以考虑使用线程锁了.本篇体验线程锁的各种用法以及线程死锁.主要包括: ※ 使用lock处理数据同步※ 使用Monitor.Enter和Monitor.E ...

  3. .net学习之多线程、线程死锁、线程通信 生产者消费者模式、委托的简单使用、GDI(图形设计接口)常用的方法

    1.多线程简单使用(1)进程是不执行代码的,执行代码的是线程,一个进程默认有一个线程(2)线程默认情况下都是前台线程,要所有的前台线程退出以后程序才会退出,进程里默认的线程我们叫做主线程或者叫做UI线 ...

  4. 对象及变量的并发访问(同步方法、同步代码块、对class进行加锁、线程死锁)&内部类的基本用法

    主要学习多线程的并发访问,也就是使得线程安全. 同步的单词为synchronized,异步的单词为asynchronized 同步主要就是通过锁的方式实现,一种就是隐式锁,另一种是显示锁Lock,本节 ...

  5. Python并发编程-进程 线程 同步锁 线程死锁和递归锁

    进程是最小的资源单位,线程是最小的执行单位 一.进程 进程:就是一个程序在一个数据集上的一次动态执行过程. 进程由三部分组成: 1.程序:我们编写的程序用来描述进程要完成哪些功能以及如何完成 2.数据 ...

  6. Atitit.线程 死锁 跑飞 的检测与自动解除 与手动解除死锁 java c# .net php javascript.

    Atitit.线程 死锁 跑飞 的检测与自动解除 与手动解除死锁 java c# .net php javascript. 1. 现象::主程序卡住无反应,多行任务不往下执行 1 2. 原因::使用j ...

  7. Python3 进程 线程 同步锁 线程死锁和递归锁

    进程是最小的资源单位,线程是最小的执行单位 一.进程 进程:就是一个程序在一个数据集上的一次动态执行过程. 进程由三部分组成: 1.程序:我们编写的程序用来描述进程要完成哪些功能以及如何完成 2.数据 ...

  8. java 线程死锁的检测

    java 线程死锁的检测   例子程序: import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executo ...

  9. troubleshoot之:用control+break解决线程死锁问题

    目录 简介 死锁的代码 control+break命令 Full thread dump 死锁检测 Heap信息 总结 简介 如果我们在程序中遇到线程死锁的时候,该怎么去解决呢? 本文将会从一个实际的 ...

随机推荐

  1. xpath定位元素

    @ 表示包含的属性,如@id表示包含id属性的标签[] 表示索引.. 表示父节点 --------------------------------------------------- //* #定位 ...

  2. 小D课堂 - 新版本微服务springcloud+Docker教程_4-02 微服务调用方式之ribbon实战 订单调用商品服务

    笔记 2.微服务调用方式之ribbon实战 订单调用商品服务     简介:实战电商项目 订单服务 调用商品服务获取商品信息         1.创建order_service项目         2 ...

  3. Linux系统管理_主题02 :管好文件(1)_2.4 链接文件_ln

    在 Linux 中,链接有两种:符号链接(symbolic link)和硬链接(hard link).  删除一个符号链接不会影响到这个符号链接指向的目标文件或目 录:  反过来,删除.移动或者重 ...

  4. ES6拓展符修改对象

    // ES6 拓展符合并两个对象let ab = { ...a, ...b }; // 等同于 let ab = Object.assign({}, a, b); // 修改对象部分属性.用户自定义的 ...

  5. Unity3D热更新之LuaFramework篇[05]--Lua脚本调用c#以及如何在Lua中使用Dotween

    在上一篇文章 Unity3D热更新之LuaFramework篇[04]--自定义UI监听方法 中,我对LuaBehaviour脚本进行了扩展,添加了两个新的UI监听方法,也提到最好能单写一个脚本处理此 ...

  6. Crunch黑客神器-创造个性字典

    先来看第一个命令: crunch 6 7 123456 -o pass.txt 是什么意思呢?我们打开终端,输入这个命令之后,crunch代表使用crunch这个工具,6代表生成的密码最小是6位数,7 ...

  7. eclipse 建立Maven java工程

    1.在项目资源管理器右键---新建---项目 2.在选择向导里选择Maven---Maven Project 3.选择默认的工作空间,下一步 4.选择箭头所示选项 5.输入组织名和工程名.点击完成

  8. maven 编译,测试,打包和安装工程

    编译工程,使用命令mvn compile 测试工程,使用命令mvn test 测试和编译工程,使用命令mvn test-compile 打包工程,使用命令mvn package,将工程打包成jar\w ...

  9. 使用PowerShell 自动安装IIS 及自动部署网站

    执行环境:Windows Server 2012 R2 安装iis核心代码,可自定义安装项 注意这里不能使用add-windowsfeature  "Web-Filtering", ...

  10. sql次级语句

    select upper(n_id) from nrc_news;select left(n_content,1) from nrc_news;select len(n_content) from n ...