You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Zero/ZeroLevel/Services/Async/AsyncLock.cs

192 lines
6.2 KiB

6 years ago
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
}
}

Powered by TurnKey Linux.