利用 Monitor.TryEnter 来规避 .NET 线程死锁

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

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

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

Monitor.TryEnter 和 Monitor.Exit 必须成对出现,为了简化代码,可以用一个实现IDisposeable的类来封装这个过程:
  1.       /// <summary>   
  2.     /// 会自动释放的锁,可设置等待超时   
  3.     /// </summary>   
  4.     public class Lock : IDisposable   
  5.     {   
  6.         /// <summary>   
  7.         /// 默认超时设置   
  8.         /// </summary>   
  9.         public static int DefaultMillisecondsTimeout = 15000; // 15S   
  10.         private object _obj;   
  11.   
  12.         /// <summary>   
  13.         /// 构造    
  14.         /// </summary>   
  15.         /// <param name="obj">想要锁住的对象</param>   
  16.         public Lock(object obj)   
  17.         {   
  18.             TryGet(obj, DefaultMillisecondsTimeout, true);   
  19.         }   
  20.   
  21.         /// <summary>   
  22.         /// 构造   
  23.         /// </summary>   
  24.         /// <param name="obj">想要锁住的对象</param>   
  25.         /// <param name="millisecondsTimeout">超时设置</param>   
  26.         public Lock(object obj, int millisecondsTimeout)   
  27.         {   
  28.             TryGet(obj, millisecondsTimeout, true);   
  29.         }   
  30.   
  31.         /// <summary>   
  32.         /// 构造   
  33.         /// </summary>   
  34.         /// <param name="obj">想要锁住的对象</param>   
  35.         /// <param name="millisecondsTimeout">超时设置</param>   
  36.         /// <param name="throwTimeoutException">是否抛出超时异常</param>   
  37.         public Lock(object obj, int millisecondsTimeout, bool throwTimeoutException)   
  38.         {   
  39.             TryGet(obj, millisecondsTimeout, throwTimeoutException);   
  40.         }   
  41.   
  42.         private void TryGet(object obj, int millisecondsTimeout, bool throwTimeoutException)   
  43.         {   
  44.             if (Monitor.TryEnter(obj, millisecondsTimeout))   
  45.             {   
  46.                 _obj = obj;   
  47.             }   
  48.             else  
  49.             {   
  50.                 if (throwTimeoutException)   
  51.                 {   
  52.                     throw new TimeoutException();   
  53.                 }   
  54.             }   
  55.         }   
  56.   
  57.         /// <summary>   
  58.         /// 销毁,并释放锁   
  59.         /// </summary>   
  60.         public void Dispose()   
  61.         {   
  62.             if (_obj != null)   
  63.             {   
  64.                 Monitor.Exit(_obj);   
  65.             }   
  66.         }   
  67.   
  68.         /// <summary>   
  69.         /// 获取在获取锁时是否发生等待超时   
  70.         /// </summary>   
  71.         public bool IsTimeout   
  72.         {   
  73.             get  
  74.             {   
  75.                 return _obj == null;   
  76.   
  77.             }   
  78.         }   
  79.     }  

调用例子:
  1. using (Lock l = new Lock(obj, 10000))   
  2. {   
  3.     ....    
  4. }  

这样在代码离开 using 块后,会自动执行 Monitor.Exit释放锁。
posted @ 2012-03-05 02:19  therockthe  阅读(686)  评论(0)    收藏  举报