There are a couple of problems with your code.
Timeout property
You have overcomplicated your implementation. Also using the DateTime.Now to determine whether the time has elapsed in a multi-threaded environment can cause thread safety issues.
Monitor.TryEnter can receive a timeout parameter. You don't need these StartTime and EndTime properties only just a simple TimeSpan which represents the timeout duration.
Lock method
You don't execute the provided action. Also in .NET it is suggested to always pass an EventArgs derived class to an event handler invocation.
lack of IDisposable interface
That would allow you to use your code inside a using block.
Here is a modified implementation which could be still improved but addresses the above-mentioned problems.
LockEventArgs
public class LockEventArgs : EventArgs
{
public object LockedObject { get; }
public bool Success { get; }
public string? Message { get; }
public LockEventArgs(object lockedObject, bool success, string? message)
{
LockedObject = lockedObject;
Success = success;
Message = message;
}
}
GenericLock
public class GenericLock<T> : IDisposable where T : notnull
{
private readonly T _value;
private readonly TimeSpan _timeout;
private bool _disposed;
private bool LockResult => Monitor.TryEnter(_value, _timeout);
public event EventHandler<LockEventArgs>? LockAcquired;
public event EventHandler<LockEventArgs>? LockReleased;
public event EventHandler<LockEventArgs>? LockFailed;
public GenericLock(T value, TimeSpan timeout)
{
_value = value;
_timeout = timeout;
_disposed = false;
}
public void Lock(Action action)
{
if (_disposed) throw new ObjectDisposedException(nameof(GenericLock<T>));
if (LockResult)
{
try
{
var args = new LockEventArgs(_value, true, null);
LockAcquired?.Invoke(this, args);
action();
}
finally
{
Monitor.Exit(_value);
var args = new LockEventArgs(_value, true, null);
LockReleased?.Invoke(this, args);
}
}
else
{
var args = new LockEventArgs(_value, false, "Lock acquisition timed out.");
LockFailed?.Invoke(this, args);
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
if (Monitor.IsEntered(_value))
{
Monitor.Exit(_value);
var args = new LockEventArgs(_value, true, "Lock released via Dispose.");
LockReleased?.Invoke(this, args);
}
}
_disposed = true;
}
}
~GenericLock()
=>Dispose(disposing: false);
}
Usage
var obj = new object();
using (var lockObject = new GenericLock<object>(obj, TimeSpan.FromSeconds(5)))
{
lockObject.Lock(() =>
{
lockObject.LockFailed += (sender, e) => {
Console.WriteLine($"Failed to acquire lock due to {e.Message}");
};
//
});
}