mirror of https://github.com/ogoun/Zero.git
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.
192 lines
6.2 KiB
192 lines
6.2 KiB
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
|
|
}
|
|
}
|