using System ;
using System.Diagnostics ;
using System.Threading ;
using System.Threading.Tasks ;
namespace ZeroLevel.Services.Async
{
/// <summary>
/// A mutual exclusion lock that is compatible with async. Note that this lock is <b>not</b> recursive!
/// </summary>
[DebuggerDisplay("Id = {Id}, Taken = {_taken}")]
[DebuggerTypeProxy(typeof(DebugView))]
public sealed class AsyncLock
{
/// <summary>
/// Whether the lock is taken by a task.
/// </summary>
private bool _taken ;
/// <summary>
/// The queue of TCSs that other tasks are awaiting to acquire the lock.
/// </summary>
private readonly IAsyncWaitQueue < IDisposable > _queue ;
/// <summary>
/// The semi-unique identifier for this instance. This is 0 if the id has not yet been created.
/// </summary>
private int _id ;
/// <summary>
/// The object used for mutual exclusion.
/// </summary>
private readonly object _mutex ;
/// <summary>
/// Creates a new async-compatible mutual exclusion lock.
/// </summary>
public AsyncLock ( )
: this ( new DefaultAsyncWaitQueue < IDisposable > ( ) )
{
}
/// <summary>
/// Creates a new async-compatible mutual exclusion lock using the specified wait queue.
/// </summary>
/// <param name="queue">The wait queue used to manage waiters.</param>
public AsyncLock ( IAsyncWaitQueue < IDisposable > queue )
{
_queue = queue ;
_mutex = new object ( ) ;
}
/// <summary>
/// Gets a semi-unique identifier for this asynchronous lock.
/// </summary>
public int Id
{
get { return IdManager < AsyncLock > . GetId ( ref _id ) ; }
}
/// <summary>
/// Asynchronously acquires the lock. Returns a disposable that releases the lock when disposed.
/// </summary>
/// <param name="cancellationToken">The cancellation token used to cancel the lock. If this is already set, then this method will attempt to take the lock immediately (succeeding if the lock is currently available).</param>
/// <returns>A disposable that releases the lock when disposed.</returns>
public Task < IDisposable > LockAsync ( CancellationToken cancellationToken )
{
Task < IDisposable > ret ;
lock ( _mutex )
{
if ( ! _taken )
{
// If the lock is available, take it immediately.
_taken = true ;
ret = TaskShim . FromResult < IDisposable > ( new Key ( this ) ) ;
}
else
{
// Wait for the lock to become available or cancellation.
ret = _queue . Enqueue ( cancellationToken ) ;
}
}
return ret ;
}
/// <summary>
/// Synchronously acquires the lock. Returns a disposable that releases the lock when disposed. This method may block the calling thread.
/// </summary>
/// <param name="cancellationToken">The cancellation token used to cancel the lock. If this is already set, then this method will attempt to take the lock immediately (succeeding if the lock is currently available).</param>
public IDisposable Lock ( CancellationToken cancellationToken )
{
Task < IDisposable > enqueuedTask ;
lock ( _mutex )
{
if ( ! _taken )
{
_taken = true ;
return new Key ( this ) ;
}
enqueuedTask = _queue . Enqueue ( cancellationToken ) ;
}
return enqueuedTask . WaitAndUnwrapException ( ) ;
}
/// <summary>
/// Asynchronously acquires the lock. Returns a disposable that releases the lock when disposed.
/// </summary>
/// <returns>A disposable that releases the lock when disposed.</returns>
public Task < IDisposable > LockAsync ( )
{
return LockAsync ( CancellationToken . None ) ;
}
/// <summary>
/// Synchronously acquires the lock. Returns a disposable that releases the lock when disposed. This method may block the calling thread.
/// </summary>
public IDisposable Lock ( )
{
return Lock ( CancellationToken . None ) ;
}
/// <summary>
/// Releases the lock.
/// </summary>
internal void ReleaseLock ( )
{
IDisposable finish = null ;
lock ( _mutex )
{
if ( _queue . IsEmpty )
_taken = false ;
else
finish = _queue . Dequeue ( new Key ( this ) ) ;
}
if ( finish ! = null )
finish . Dispose ( ) ;
}
/// <summary>
/// The disposable which releases the lock.
/// </summary>
private sealed class Key : IDisposable
{
/// <summary>
/// The lock to release.
/// </summary>
private AsyncLock _asyncLock ;
/// <summary>
/// Creates the key for a lock.
/// </summary>
/// <param name="asyncLock">The lock to release. May not be <c>null</c>.</param>
public Key ( AsyncLock asyncLock )
{
_asyncLock = asyncLock ;
}
/// <summary>
/// Release the lock.
/// </summary>
public void Dispose ( )
{
if ( _asyncLock = = null )
return ;
_asyncLock . ReleaseLock ( ) ;
_asyncLock = null ;
}
}
// ReSharper disable UnusedMember.Local
[DebuggerNonUserCode]
private sealed class DebugView
{
private readonly AsyncLock _mutex ;
public DebugView ( AsyncLock mutex )
{
_mutex = mutex ;
}
public int Id { get { return _mutex . Id ; } }
public bool Taken { get { return _mutex . _taken ; } }
public IAsyncWaitQueue < IDisposable > WaitQueue { get { return _mutex . _queue ; } }
}
// ReSharper restore UnusedMember.Local
}
}