using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; namespace ZeroLevel.Services.Async { /// /// An async-compatible monitor. /// [DebuggerDisplay("Id = {Id}, ConditionVariableId = {_conditionVariable.Id}")] public sealed class AsyncMonitor { /// /// The lock. /// private readonly AsyncLock _asyncLock; /// /// The condition variable. /// private readonly AsyncConditionVariable _conditionVariable; /// /// Constructs a new monitor. /// public AsyncMonitor(IAsyncWaitQueue lockQueue, IAsyncWaitQueue conditionVariableQueue) { _asyncLock = new AsyncLock(lockQueue); _conditionVariable = new AsyncConditionVariable(_asyncLock, conditionVariableQueue); } /// /// Constructs a new monitor. /// public AsyncMonitor() : this(new DefaultAsyncWaitQueue(), new DefaultAsyncWaitQueue()) { } /// /// Gets a semi-unique identifier for this monitor. /// public int Id { get { return _asyncLock.Id; } } /// /// Asynchronously enters the monitor. Returns a disposable that leaves the monitor when disposed. /// /// The cancellation token used to cancel the enter. If this is already set, then this method will attempt to enter the monitor immediately (succeeding if the monitor is currently available). /// A disposable that leaves the monitor when disposed. public Task EnterAsync(CancellationToken cancellationToken) { return _asyncLock.LockAsync(cancellationToken); } /// /// Synchronously enters the monitor. Returns a disposable that leaves the monitor when disposed. This method may block the calling thread. /// /// The cancellation token used to cancel the enter. If this is already set, then this method will attempt to enter the monitor immediately (succeeding if the monitor is currently available). public IDisposable Enter(CancellationToken cancellationToken) { return _asyncLock.Lock(cancellationToken); } /// /// Asynchronously enters the monitor. Returns a disposable that leaves the monitor when disposed. /// /// A disposable that leaves the monitor when disposed. public Task EnterAsync() { return EnterAsync(CancellationToken.None); } /// /// Asynchronously enters the monitor. Returns a disposable that leaves the monitor when disposed. This method may block the calling thread. /// public IDisposable Enter() { return Enter(CancellationToken.None); } /// /// Asynchronously waits for a pulse signal on this monitor. The monitor MUST already be entered when calling this method, and it will still be entered when this method returns, even if the method is cancelled. This method internally will leave the monitor while waiting for a notification. /// /// The cancellation signal used to cancel this wait. public Task WaitAsync(CancellationToken cancellationToken) { return _conditionVariable.WaitAsync(cancellationToken); } /// /// Asynchronously waits for a pulse signal on this monitor. This method may block the calling thread. The monitor MUST already be entered when calling this method, and it will still be entered when this method returns, even if the method is cancelled. This method internally will leave the monitor while waiting for a notification. /// /// The cancellation signal used to cancel this wait. public void Wait(CancellationToken cancellationToken) { _conditionVariable.Wait(cancellationToken); } /// /// Asynchronously waits for a pulse signal on this monitor. The monitor MUST already be entered when calling this method, and it will still be entered when this method returns. This method internally will leave the monitor while waiting for a notification. /// public Task WaitAsync() { return WaitAsync(CancellationToken.None); } /// /// Asynchronously waits for a pulse signal on this monitor. This method may block the calling thread. The monitor MUST already be entered when calling this method, and it will still be entered when this method returns. This method internally will leave the monitor while waiting for a notification. /// public void Wait() { Wait(CancellationToken.None); } /// /// Sends a signal to a single task waiting on this monitor. The monitor MUST already be entered when calling this method, and it will still be entered when this method returns. /// public void Pulse() { _conditionVariable.Notify(); } /// /// Sends a signal to all tasks waiting on this monitor. The monitor MUST already be entered when calling this method, and it will still be entered when this method returns. /// public void PulseAll() { _conditionVariable.NotifyAll(); } } }