Last active
August 6, 2018 13:54
-
-
Save nathanpjones/64c139ba4a909d30940a to your computer and use it in GitHub Desktop.
Monitored Lock
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
/* | |
* This is an example console application to illustrate the usage | |
* of the MonitoredLock class and technique. | |
*/ | |
using System; | |
using System.Diagnostics; | |
using System.Threading; | |
namespace MonitoredLock | |
{ | |
public static class Example | |
{ | |
private static object _lockObj = new object(); | |
public static object LockObj | |
{ | |
get { return _lockObj;} | |
} | |
public static void OtherStuff() | |
{ | |
Thread.Sleep(1000); | |
using (new MonitoredLock(_lockObj, "LockObj")) | |
{ | |
Console.WriteLine(); | |
Console.WriteLine("** Entered other thread's locked statement"); | |
Console.WriteLine(); | |
} | |
Thread.Sleep(1000); | |
using (new MonitoredLock(_lockObj, "LockObj")) | |
{ | |
Console.WriteLine(); | |
Console.WriteLine("** Entered other thread's locked statement AGAIN"); | |
Console.WriteLine(); | |
} | |
} | |
public static void Main() | |
{ | |
//Trace.Listeners.Add(new ConsoleTraceListener()); | |
//Trace.Indent(); | |
Console.WriteLine("** Started!"); | |
Console.WriteLine(); | |
var thread = new Thread(OtherStuff) | |
{ | |
Name = "Other Thread", | |
IsBackground = false | |
}; | |
thread.Start(); | |
try | |
{ | |
using (MonitoredLock.From(() => LockObj)) | |
{ | |
Console.WriteLine(); | |
Console.WriteLine("** Starting body of Main lock"); | |
Console.WriteLine(); | |
Thread.Sleep(1500); | |
using (MonitoredLock.From(() => LockObj)) | |
{ | |
Console.WriteLine("*** Nested lock!"); | |
} | |
Thread.Sleep(1500); | |
Console.WriteLine(); | |
Console.WriteLine("** Ending body of Main lock"); | |
Console.WriteLine(); | |
} | |
Thread.Sleep(500); // allow other thread to lock | |
using (MonitoredLock.From(() => LockObj)) | |
{ | |
Console.WriteLine(); | |
Console.WriteLine("** Starting body of SECOND Main lock"); | |
Console.WriteLine(); | |
Thread.Sleep(1500); | |
throw new Exception(); | |
} | |
} | |
catch (Exception e) | |
{ | |
Console.WriteLine("** Caught exception: " + e); | |
} | |
Console.ReadKey(true); | |
} | |
} | |
} |
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
using System; | |
using System.Diagnostics; | |
using System.Diagnostics.CodeAnalysis; | |
using System.Linq.Expressions; | |
using System.Runtime.CompilerServices; | |
using System.Runtime.InteropServices; | |
using System.Threading; | |
namespace MonitoredLock | |
{ | |
/// <summary> | |
/// Used to simulate syntax of 'lock' statement but with logging of | |
/// entry and exit of the lock. Intended to be used with the | |
/// 'using' statement. | |
/// </summary> | |
[DebuggerStepThrough] | |
public class MonitoredLock : IDisposable | |
{ | |
private readonly static ConditionalWeakTable<object, Counter> LockDepthCounter = new ConditionalWeakTable<object, Counter>(); | |
private readonly string _callerFilePath; | |
private readonly int _callerLine; | |
private readonly string _callerName; | |
private Counter _lockDepthCounter; | |
private readonly string _lockName; | |
private readonly object _lockObj; | |
private readonly bool _lockWasTaken; | |
private bool _disposed; | |
public MonitoredLock(object lockObj, string lockName, [CallerFilePath] string callerFilePath = null, [CallerMemberName] string callerName = null, [CallerLineNumber] int callerLine = -1) | |
{ | |
if (lockObj == null) throw new ArgumentNullException("lockObj"); | |
_lockObj = lockObj; | |
_lockName = lockName ?? "<none>"; | |
_callerFilePath = callerFilePath; | |
_callerName = callerName; | |
_callerLine = callerLine; | |
// Log that we're about to enter so it's clear who's waiting | |
// if we can't immediately take out the lock | |
LogMessage("WAIT ", false, false); | |
// Actually take out the lock | |
Monitor.Enter(_lockObj, ref _lockWasTaken); | |
// Increment depth counter and log entry | |
IncrementDepthCounter(); | |
LogMessage("ENTER", false, false); | |
} | |
/// <summary> | |
/// <see cref="IDisposable"/> implementation. | |
/// </summary> | |
public void Dispose() | |
{ | |
Dispose(true); | |
GC.SuppressFinalize(this); | |
} | |
/// <summary> | |
/// Dispose is where the lock is exited. | |
/// </summary> | |
protected virtual void Dispose(bool disposing) | |
{ | |
if (_disposed) return; | |
_disposed = true; | |
if (_lockWasTaken) | |
{ | |
// Write exit message and decrement before exiting lock to ensure that | |
// it will output before the next entry into the lock. | |
LogMessage("EXIT ", true, disposing); | |
DecrementDepthCounter(); | |
Monitor.Exit(_lockObj); | |
} | |
} | |
/// <summary> | |
/// Locates the counter for the lock object and increments it or | |
/// creates a new counter and initializes it to 1. | |
/// </summary> | |
[Conditional("TRACE")] | |
private void IncrementDepthCounter() | |
{ | |
// Manage entry depth | |
if (LockDepthCounter.TryGetValue(_lockObj, out _lockDepthCounter)) | |
{ | |
// Increment existing counter | |
if (_lockWasTaken) _lockDepthCounter.Value++; | |
} | |
else | |
{ | |
// Start new counter | |
_lockDepthCounter = new Counter() { Value = 1 }; | |
LockDepthCounter.Add(_lockObj, _lockDepthCounter); | |
} | |
} | |
/// <summary> | |
/// Decrements a counter if there is one. | |
/// </summary> | |
[Conditional("TRACE")] | |
private void DecrementDepthCounter() | |
{ | |
if (_lockDepthCounter == null) return; | |
// Decrement depth | |
_lockDepthCounter.Value--; | |
} | |
/// <summary> | |
/// Log the current action. | |
/// </summary> | |
/// <param name="action">Name of the action.</param> | |
/// <param name="isExit">Whether or not this is the exit message--slightly different formatting for this.</param> | |
/// <param name="isDisposing"></param> | |
[Conditional("TRACE")] | |
private void LogMessage(string action, bool isExit, bool isDisposing) | |
{ | |
var currentThread = Thread.CurrentThread; | |
var exitInFinalizer = isExit && !isDisposing; | |
var msg = string.Format("{0} Lock '{1}'{2} on thread '{3}' [{4};{5}]{6} at {7} in {8}:line {9}{10}", | |
action, | |
_lockName, | |
null != _lockDepthCounter ? string.Format(" at depth {0}", _lockDepthCounter.Value) : null, | |
currentThread.Name ?? "<No Name>", | |
GetCurrentThreadId(), | |
currentThread.ManagedThreadId, | |
isExit ? " entered" : "", | |
_callerName, | |
_callerFilePath, | |
_callerLine, | |
exitInFinalizer ? " - WARNING: Lock was exited in finalizer!" : ""); | |
if (exitInFinalizer) | |
Trace.TraceWarning(msg); | |
else | |
Trace.TraceInformation(msg); | |
} | |
/// <summary> | |
/// Create a <see cref="MonitoredLock"/> from an expression. | |
/// </summary> | |
/// <param name="memberExpression">Expression from which the object reference and name of the lock object are obtained.</param> | |
[SuppressMessage("ReSharper", "ExplicitCallerInfoArgument")] | |
public static MonitoredLock From<T>(Expression<Func<T>> memberExpression, [CallerFilePath] string callerFilePath = null, [CallerMemberName] string callerName = null, [CallerLineNumber] int callerLine = -1) | |
{ | |
var expressionBody = (MemberExpression)memberExpression.Body; | |
return new MonitoredLock(memberExpression.Compile().Invoke(), expressionBody.Member.Name, callerFilePath, callerName, callerLine); | |
} | |
/// <summary> | |
/// Implemente a finalizer because want to make absolutely sure the lock has been cleared. | |
/// </summary> | |
~MonitoredLock() | |
{ | |
Dispose(false); | |
} | |
/// <summary> | |
/// Have to call into OS to get real thread id. May not be desirable on non-Windows platforms. | |
/// </summary> | |
[DllImport("kernel32.dll")] | |
private static extern uint GetCurrentThreadId(); | |
/// <summary> | |
/// This class only exists to provide a reference type for use with the ConditionalWeakTable above. | |
/// </summary> | |
private class Counter | |
{ | |
public int Value { get; set; } | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment