/// <summary> /// Attempts to dequeue an item. /// </summary> /// <param name="cancellationToken">A cancellation token that can be used to abort the dequeue operation. If <paramref name="abort"/> is not <c>null</c>, then this token must include signals from the <paramref name="abort"/> object.</param> /// <param name="abort">A synchronization object used to cancel related dequeue operations. May be <c>null</c> if this is the only dequeue operation.</param> internal async Task <DequeueResult> TryDequeueAsync(CancellationToken cancellationToken, TaskCompletionSource abort) { try { using (await _mutex.LockAsync().ConfigureAwait(false)) { while (!_completed.IsCancellationRequested && Empty) { await _completedOrNotEmpty.WaitAsync(cancellationToken).ConfigureAwait(false); } if (_completed.IsCancellationRequested && Empty) { return(FalseResult); } if (abort != null && !abort.TrySetCanceled()) { return(FalseResult); } var item = _queue.Dequeue(); _notFull.Notify(); return(new DequeueResult(this, item)); } } catch (OperationCanceledException) { return(FalseResult); } }
/// <summary> /// Attempts to enqueue an item. /// </summary> /// <param name="item">The item to enqueue.</param> /// <param name="cancellationToken">A cancellation token that can be used to abort the enqueue operation. If <paramref name="abort"/> is not <c>null</c>, then this token must include signals from the <paramref name="abort"/> object.</param> /// <param name="abort">A synchronization object used to cancel related enqueue operations. May be <c>null</c> if this is the only enqueue operation.</param> internal async Task <AsyncProducerConsumerQueue <T> > TryEnqueueAsync(T item, CancellationToken cancellationToken, TaskCompletionSource abort) { try { using (var combinedToken = CancellationTokenHelpers.Normalize(_completed.Token, cancellationToken)) using (await _mutex.LockAsync().ConfigureAwait(false)) { // Wait for the queue to be not full. while (Full) { await _notFull.WaitAsync(combinedToken.Token).ConfigureAwait(false); } // Explicitly check whether the queue has been marked complete to prevent a race condition where notFull is signalled at the same time the queue is marked complete. if (_completed.IsCancellationRequested) { return(null); } // Set the abort signal. If another queue has already set the abort signal, then abort. if (abort != null && !abort.TrySetCanceled()) { return(null); } _queue.Enqueue(item); _completedOrNotEmpty.Notify(); return(this); } } catch (OperationCanceledException) { return(null); } }
public void WaitAsync_Notified_IsCompleted() { Test.Async(async () => { var mutex = new AsyncLock(); var cv = new AsyncConditionVariable(mutex); await mutex.LockAsync(); var task = cv.WaitAsync(); await TaskShim.Run(async () => { using (await mutex.LockAsync()) { cv.Notify(); } }); await task; }); }
public void WaitAsync_AfterNotify_IsNotCompleted() { Test.Async(async () => { var mutex = new AsyncLock(); var cv = new AsyncConditionVariable(mutex); await TaskShim.Run(async () => { using (await mutex.LockAsync()) { cv.Notify(); } }); await mutex.LockAsync(); var task = cv.WaitAsync(); await AssertEx.NeverCompletesAsync(task); }); }
/// <summary> /// 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. /// </summary> public void Pulse() { _conditionVariable.Notify(); }
public void MultipleWaits_Notify_OneIsCompleted() { Test.Async(async () => { var mutex = new AsyncLock(); var cv = new AsyncConditionVariable(mutex); var key = await mutex.LockAsync(); var task1 = cv.WaitAsync(); var __ = task1.ContinueWith(_ => key.Dispose()); await mutex.LockAsync(); var task2 = cv.WaitAsync(); await TaskShim.Run(async () => { using (await mutex.LockAsync()) { cv.Notify(); } }); await task1; await AssertEx.NeverCompletesAsync(task2); }); }