Example #1
0
        /// <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();
            }
        }
Example #2
0
        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);
        }
Example #3
0
        /// <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);
        }