利用 Monitor.TryEnter 来规避 .NET 线程死锁
利用 Monitor.TryEnter 来规避 .NET 线程死锁
在开发多线程的应用程序时,我们会大量用到 lock (...) {} 块。如果 lock 的对象比较多,非常容易发生死锁。死锁的发生很难预料,而且一旦发生在界面线程上,界面就不再刷新响和应用户输入;如果发生在后台线程,后台线程也就阻塞不工作了,死锁必然会导致应用程序不可用。在.NET里发生死锁的原因是什么?
以 C# 为例,通常 lock 语句是被转化为对一个资源的无限长时间的等待,所以一旦资源被占用而又永不释放,那么必然死锁。
那么如何规避的危害呢?应用程序应该避免 lock(obj) 块,推荐使用 Monitor.TryEnter(obj, millisecondsTimeout) 代替,二者的第一个参数意义相同,而后者还可以设置等待超时时间,一旦在限定的时间内无法获得锁,那么 TryEnter 就会返回 false。这样就不会造成死锁,无法获得资源,业务程序可以采取重试或抛异常的方式进行善后处理。
Monitor.TryEnter 和 Monitor.Exit 必须成对出现,为了简化代码,可以用一个实现IDisposeable的类来封装这个过程:
调用例子:
这样在代码离开 using 块后,会自动执行 Monitor.Exit释放锁。
在开发多线程的应用程序时,我们会大量用到 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释放锁。
浙公网安备 33010602011771号