Last active
August 29, 2015 14:02
-
-
Save xanathar/1f07acf3db24d451c6d6 to your computer and use it in GitHub Desktop.
Wrapper of Monitor class for easier deadlock debug
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// original source is likely to be https://github.com/thinkpixellab/bot/blob/master/net40-client/Core/LockHelper.cs although I can't verify it anymore. | |
/// <summary> | |
/// Provides services of Monitor static class, but while allowing better debugging of deadlocks. | |
/// </summary> | |
public class LockHelper | |
{ | |
/// <summary> | |
/// Creates a new instance of LockHelper. | |
/// </summary> | |
/// <param name="name">The name to give the helper. Cannot be null or empty.</param> | |
/// <exception cref="ArgumentOutOfRangeException">If name is null or empty.</exception> | |
public LockHelper(string name) | |
{ | |
if (string.IsNullOrEmpty(name)) | |
{ | |
throw new ArgumentOutOfRangeException("name"); | |
} | |
m_name = name; | |
} | |
/// <summary> | |
/// Aquires the lock. | |
/// </summary> | |
/// <returns>An IDisposable that can be used in a C# 'lock' block.</returns> | |
/// <example>using(lockHelper.GetLock(){ //do work here }</example> | |
public IDisposable GetLock() | |
{ | |
Enter(); | |
return new Unlocker(this); | |
} | |
/// <summary> | |
/// Aquires the lock using a Monitor.Enter like semantic. | |
/// </summary> | |
public void Enter() | |
{ | |
Monitor.Enter(m_lockObject); | |
Thread currentThread = Thread.CurrentThread; | |
m_threadStack.Push(currentThread); | |
if (string.IsNullOrEmpty(currentThread.Name)) | |
{ | |
m_owningThreadName = string.Format("Unnamed - ManagedThreadId:{0}", currentThread.ManagedThreadId); | |
} | |
else | |
{ | |
m_owningThreadName = currentThread.Name; | |
} | |
} | |
/// <summary> | |
/// Exits the lock using a Monitor.Exit like semantic. | |
/// </summary> | |
public void Exit() | |
{ | |
unlock(); | |
} | |
/// <summary> | |
/// Proxy for the Monitor.Pulse method on the current instance. | |
/// </summary> | |
public void Pulse() | |
{ | |
Monitor.Pulse(m_lockObject); | |
} | |
/// <summary> | |
/// Proxy for the Monitor.Wait method on the current instance. | |
/// </summary> | |
public void Wait() | |
{ | |
Monitor.Wait(m_lockObject); | |
} | |
/// <summary> | |
/// Check to see if the calling thread hold this lock on this LockHelper. | |
/// </summary> | |
/// <returns>True if it holds the lock. False otherwise.</returns> | |
public bool CheckAccess() | |
{ | |
return (m_threadStack.Count > 0) && | |
(Thread.CurrentThread == m_threadStack.Peek()); | |
} | |
/// <summary> | |
/// Throws an exception if CheckAccess() returns false. | |
/// </summary> | |
/// <exception cref="CommonException">If CheckAccess would throw an exception.</exception> | |
public void VerifyAccess() | |
{ | |
if (!CheckAccess()) | |
{ | |
throw new Exception("Code was run that does not have the nessesary lock."); | |
} | |
} | |
private void unlock() | |
{ | |
VerifyAccess(); | |
//ideally, this work would be done 'after' Exit, to make sure 'Exit' succeeds | |
//BUT, it must be done while the object is locked, or we'll have conflicts | |
//with the code that is run after Monitor.Enter | |
m_threadStack.Pop(); | |
if (m_threadStack.Count > 0) | |
{ | |
Thread currentThread = m_threadStack.Peek(); | |
if (string.IsNullOrEmpty(currentThread.Name)) | |
{ | |
m_owningThreadName = string.Format("Unnamed - ManagedThreadId:{0}", currentThread.ManagedThreadId); | |
} | |
else | |
{ | |
m_owningThreadName = currentThread.Name; | |
} | |
} | |
else | |
{ | |
m_owningThreadName = null; | |
} | |
Monitor.Exit(m_lockObject); | |
} | |
private string m_owningThreadName; | |
private readonly Stack<Thread> m_threadStack = new Stack<Thread>(); | |
private readonly object m_lockObject = new object(); | |
private readonly string m_name; | |
private class Unlocker : IDisposable | |
{ | |
public Unlocker(LockHelper owner) | |
{ | |
if (owner == null) | |
{ | |
throw new ArgumentNullException("owner"); | |
} | |
m_owner = owner; | |
} | |
public void Dispose() | |
{ | |
lock (m_lockObject) | |
{ | |
if (m_owner != null) | |
{ | |
m_owner.unlock(); | |
//do this after the unlock to ensure that it succeeds | |
m_owner = null; | |
} | |
else | |
{ | |
throw new Exception("Cannot Dispose the object returned from GetLock more than once."); | |
} | |
} | |
} | |
private readonly object m_lockObject = new object(); | |
private LockHelper m_owner; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment