/// <summary> /// Cancels all outstanding dequeue tasks for the specified CancellationToken. /// </summary> /// <param name="state">A <see cref="Tuple{AsyncQueue, CancellationToken}"/> instance.</param> private static void CancelDequeuers(object state) { var tuple = (Tuple <AsyncQueue <T>, CancellationToken>)state; AsyncQueue <T> that = tuple.Item1; CancellationToken ct = tuple.Item2; CancellableDequeuers cancelledAwaiters = null; lock (that.syncObject) { if (that.dequeuingTasks != null && that.dequeuingTasks.TryGetValue(ct, out cancelledAwaiters)) { that.dequeuingTasks.Remove(ct); } } // This work can invoke external code and mustn't happen within our private lock. Assumes.False(Monitor.IsEntered(that.syncObject)); // important because we'll transition a task to complete. if (cancelledAwaiters != null) { foreach (var awaiter in cancelledAwaiters) { awaiter.SetCanceled(); } cancelledAwaiters.Dispose(); } }
public Task <T> DequeueAsync(CancellationToken cancellationToken = default(CancellationToken)) { var tcs = new TaskCompletionSource <T>(); CancellableDequeuers existingAwaiters = null; bool newDequeuerObjectAllocated = false; lock (this.syncObject) { if (cancellationToken.IsCancellationRequested) { // It's OK to transition this task within the lock, // since we only just created the task in this method so // it couldn't possibly have any continuations that would inline // inside our lock. tcs.TrySetCanceled(cancellationToken); } else { T value; if (this.TryDequeueInternal(null, out value)) { tcs.SetResult(value); } else { if (this.dequeuingTasks == null) { this.dequeuingTasks = new Dictionary <CancellationToken, CancellableDequeuers>(); } if (!this.dequeuingTasks.TryGetValue(cancellationToken, out existingAwaiters)) { existingAwaiters = new CancellableDequeuers(this); newDequeuerObjectAllocated = true; this.dequeuingTasks[cancellationToken] = existingAwaiters; } existingAwaiters.AddCompletionSource(tcs); } } } if (newDequeuerObjectAllocated) { Assumes.NotNull(existingAwaiters); var cancellationRegistration = cancellationToken.Register( state => CancelDequeuers(state), Tuple.Create(this, cancellationToken)); existingAwaiters.SetCancellationRegistration(cancellationRegistration); } this.CompleteIfNecessary(); return(tcs.Task); }
/// <summary> /// Adds an element to the tail of the queue if it has not yet completed. /// </summary> /// <param name="value">The value to add.</param> /// <returns><c>true</c> if the value was added to the queue; <c>false</c> if the queue is already completed.</returns> public bool TryEnqueue(T value) { TaskCompletionSource <T> dequeuer = null; List <CancellableDequeuers> valuesToDispose = null; lock (this.syncObject) { if (this.completeSignaled) { return(false); } if (this.dequeuingTasks != null) { foreach (var entry in this.dequeuingTasks) { CancellationToken cancellationToken = entry.Key; CancellableDequeuers dequeurs = entry.Value; // Remove the last dequeuer from the list. dequeuer = dequeurs.PopDequeuer(); if (dequeurs.IsEmpty) { this.dequeuingTasks.Remove(cancellationToken); if (valuesToDispose == null) { valuesToDispose = new List <CancellableDequeuers>(); } valuesToDispose.Add(dequeurs); } break; } } if (dequeuer == null) { // There were no waiting dequeuers, so actually add this element to our queue. if (this.queueElements == null) { this.queueElements = new Queue <T>(this.InitialCapacity); } this.queueElements.Enqueue(value); } } Assumes.False(Monitor.IsEntered(this.syncObject)); // important because we'll transition a task to complete. // It's important we dispose of these values outside the lock. if (valuesToDispose != null) { foreach (var item in valuesToDispose) { item.Dispose(); } } // We only transition this task to complete outside of our lock so // we don't accidentally inline continuations inside our lock. if (dequeuer != null) { // There was already someone waiting for an element to process, so // immediately allow them to begin work and skip our internal queue. dequeuer.SetResult(value); } this.OnEnqueued(value, dequeuer != null); return(true); }