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