using System;
using System.Threading;
using Pfz.Threading;
namespace Pfz.Extensions.MonitorLockExtensions
{
/// <summary>
/// Adds methods to lock any object using Monitor methods easily and
/// always with time-out, so you can avoid dead-locks.
/// See PfzLockConfiguration class if you want to log dead-locks.
/// </summary>
public static class PfzMonitorLockExtensions
{
#region LockWithTimeout - Disposable
/// <summary>
/// Locks an object or throws an exception if it times-out.
/// Use with the using keyword.
/// Defaults to one minute.
/// </summary>
/// <typeparam name="T">Only used to constraint objects to reference types.</typeparam>
/// <param name="item">The object to lock.</param>
/// <returns>A disposable object so you can do a using in it.</returns>
public static IDisposable LockWithTimeout<T>(this T item)
where
T: class
{
return LockWithTimeout(item, LockConfiguration.DefaultLockTimeout);
}
/// <summary>
/// Locks an object or throws an exception if it times-out.
/// Use with the using keyword.
/// </summary>
/// <typeparam name="T">Only used to constraint objects to reference types.</typeparam>
/// <param name="item">The object to lock.</param>
/// <param name="timeout">A timespan representing the timeout value.</param>
/// <returns>A disposable object so you can do a using in it.</returns>
public static IDisposable LockWithTimeout<T>(this T item, TimeSpan timeout)
where
T: class
{
IDisposable result = new p_LockWithTimeout(item);
if (Monitor.TryEnter(item, timeout))
return result;
throw LockConfiguration.i_LockTimedOutException(LockConfiguration.LockType.Monitor);
}
/// <summary>
/// Tries to acquire a lock on the given object, using the default lock-timeout.
/// In case of failure, it logs the error, but does not generates an exception. Instead, it returns
/// null.
/// </summary>
/// <typeparam name="T">The type of class to lock.</typeparam>
/// <param name="item">The item to lock.</param>
/// <returns>A disposable object, so you can release the lock, or null if the lock was not acquired.</returns>
public static IDisposable TryLockWithTimeout<T>(this T item)
where
T: class
{
return TryLockWithTimeout(item, LockConfiguration.DefaultLockTimeout);
}
/// <summary>
/// Tries to acquire a lock on the given object, using the given time-out.
/// In case of failure, it logs the error, but does not generates an exception. Instead, it returns
/// null.
/// </summary>
/// <typeparam name="T">The type of class to lock.</typeparam>
/// <param name="item">The item to lock.</param>
/// <param name="timeout">The timeout value while trying to acquire the lock.</param>
/// <returns>A disposable object, so you can release the lock, or null if the lock was not acquired.</returns>
public static IDisposable TryLockWithTimeout<T>(this T item, TimeSpan timeout)
{
IDisposable result = new p_LockWithTimeout(item);
if (Monitor.TryEnter(item))
return result;
LockConfiguration.i_LockTimedOutNoException(LockConfiguration.LockType.Monitor);
return null;
}
private sealed class p_LockWithTimeout:
IDisposable
{
internal object fItem;
public p_LockWithTimeout(object item)
{
fItem = item;
}
public void Dispose()
{
object item = fItem;
if (item != null)
{
Monitor.Exit(item);
fItem = null;
}
}
}
#endregion
#region LockWithTimeout - Action
private static readonly TimeSpan fOneSecond = new TimeSpan(0, 0, 1);
/// <summary>
/// Locks the specified item with the specified timeout.
/// If it acquires the lock, runs the given action. Note that the
/// action can be aborted, but the lock will not be held.
/// </summary>
public static void LockWithTimeout<T>(this T item, Action action)
where
T: class
{
LockWithTimeout(item, LockConfiguration.DefaultLockTimeout, action);
}
/// <summary>
/// Locks the specified item with the specified timeout.
/// If it acquires the lock, runs the given action. Note that the
/// action can be aborted, but the lock will not be held.
/// Allows you to specify the timeout value.
/// </summary>
public static void LockWithTimeout<T>(this T item, TimeSpan timeout, Action action)
where
T: class
{
if (!TryLockWithTimeout(item, timeout, action))
LockConfiguration.i_LockTimedOutException(LockConfiguration.LockType.Monitor);
}
/// <summary>
/// Tries to lock an object and then execute an action.
/// Returns if the lock was obtained and the action fully executed.
/// Be careful, as the lock is already released when the method returns.
/// </summary>
public static bool TryLockWithTimeout<T>(this T item, Action action)
where
T: class
{
return TryLockWithTimeout(item, LockConfiguration.DefaultLockTimeout, action);
}
/// <summary>
/// Tries to lock an object and then execute an action.
/// Returns if the lock was obtained and the action fully executed.
/// Be careful, as the lock is already released when the method returns.
/// </summary>
public static bool TryLockWithTimeout<T>(this T item, TimeSpan timeout, Action action)
where
T: class
{
return p_TryLockWithTimeout(item, timeout, action);
}
private static bool p_TryLockWithTimeout(object item, TimeSpan timeout, Action action)
{
if (action == null)
throw new ArgumentNullException("action");
if (timeout < TimeSpan.Zero)
throw new ArgumentException("timeout can be less than zero.", "timeout");
bool lockAcquired = false;
try
{
while (timeout > TimeSpan.Zero)
{
try
{
}
finally
{
TimeSpan timeOutToUse = fOneSecond;
if (timeout < fOneSecond)
timeOutToUse = timeout;
lockAcquired = Monitor.TryEnter(item, timeOutToUse);
}
if (lockAcquired)
{
action();
return true;
}
if (AbortSafe.WasAbortRequested)
return false;
timeout -= fOneSecond;
}
}
finally
{
if (lockAcquired)
Monitor.Exit(item);
}
return false;
}
#endregion
}
}