Esempio n. 1
0
        /// <summary>Dequeues an item, and then fixes up our state around writers and completion.</summary>
        /// <returns>The dequeued item.</returns>
        private T DequeueItemAndPostProcess()
        {
            Debug.Assert(Monitor.IsEntered(SyncObj));

            // Dequeue an item.
            T item = _items.DequeueHead();

            // If we're now empty and we're done writing, complete the channel.
            if (_doneWriting != null && _items.IsEmpty)
            {
                ChannelUtilities.Complete(_completion, _doneWriting);
            }

            // If there are any writers blocked, there's now room for at least one
            // to be promoted to have its item moved into the items queue.  We need
            // to loop while trying to complete the writer in order to find one that
            // hasn't yet been canceled (canceled writers transition to canceled but
            // remain in the physical queue).
            while (!_blockedWriters.IsEmpty)
            {
                WriterInteractor <T> w = _blockedWriters.DequeueHead();
                if (w.Success(default(VoidResult)))
                {
                    _items.EnqueueTail(w.Item);
                    return(item);
                }
            }

            // There was no blocked writer, so see if there's a WaitToWriteAsync
            // we should wake up.
            ChannelUtilities.WakeUpWaiters(ref _waitingWriters, result: true);

            // Return the item
            return(item);
        }
        private ValueTask <T> ReadAsyncCore(CancellationToken cancellationToken)
        {
            if (cancellationToken.IsCancellationRequested)
            {
                return(new ValueTask <T>(Task.FromCanceled <T>(cancellationToken)));
            }

            lock (SyncObj)
            {
                AssertInvariants();

                // If we're already completed, nothing to read.
                if (_completion.Task.IsCompleted)
                {
                    return(new ValueTask <T>(
                               _completion.Task.IsCanceled ? Task.FromCanceled <T>(new CancellationToken(true)) :
                               Task.FromException <T>(
                                   _completion.Task.IsFaulted ?
                                   ChannelUtilities.CreateInvalidCompletionException(_completion.Task.Exception.InnerException) :
                                   ChannelUtilities.CreateInvalidCompletionException())));
                }

                // If there are any blocked writers, find one to pair up with
                // and get its data.  Writers that got canceled will remain in the queue,
                // so we need to loop to skip past them.
                while (!_blockedWriters.IsEmpty)
                {
                    WriterInteractor <T> w = _blockedWriters.DequeueHead();
                    if (w.Success(default))
Esempio n. 3
0
 /// <summary>Removes all interactors from the queue, failing each.</summary>
 /// <param name="interactors">The queue of interactors to complete.</param>
 /// <param name="error">The error with which to complete each interactor.</param>
 internal static void FailInteractors <T, TInner>(Dequeue <T> interactors, Exception error) where T : Interactor <TInner>
 {
     while (!interactors.IsEmpty)
     {
         interactors.DequeueHead().Fail(error ?? CreateInvalidCompletionException());
     }
 }
Esempio n. 4
0
        private bool TryWrite(T item)
        {
            while (true)
            {
                ReaderInteractor <T>    blockedReader  = null;
                ReaderInteractor <bool> waitingReaders = null;
                lock (SyncObj)
                {
                    // If writing has already been marked as done, fail the write.
                    AssertInvariants();
                    if (_doneWriting != null)
                    {
                        return(false);
                    }

                    // If there aren't any blocked readers, just add the data to the queue,
                    // and let any waiting readers know that they should try to read it.
                    // We can only complete such waiters here under the lock if they run
                    // continuations asynchronously (otherwise the synchronous continuations
                    // could be invoked under the lock).  If we don't complete them here, we
                    // need to do so outside of the lock.
                    if (_blockedReaders.IsEmpty)
                    {
                        _items.Enqueue(item);
                        waitingReaders = _waitingReaders;
                        if (waitingReaders == null)
                        {
                            return(true);
                        }
                        _waitingReaders = null;
                    }
                    else
                    {
                        // There were blocked readers.  Grab one, and then complete it outside of the lock.
                        blockedReader = _blockedReaders.DequeueHead();
                    }
                }

                if (blockedReader != null)
                {
                    // Complete the reader.  It's possible the reader was canceled, in which
                    // case we loop around to try everything again.
                    if (blockedReader.Success(item))
                    {
                        return(true);
                    }
                }
                else
                {
                    // Wake up all of the waiters.  Since we've released the lock, it's possible
                    // we could cause some spurious wake-ups here, if we tell a waiter there's
                    // something available but all data has already been removed.  It's a benign
                    // race condition, though, as consumers already need to account for such things.
                    waitingReaders.Success(item: true);
                    return(true);
                }
            }
        }
        /// <summary>Dequeues an item, and then fixes up our state around writers and completion.</summary>
        /// <returns>The dequeued item.</returns>
        private T DequeueItemAndPostProcess()
        {
            Debug.Assert(Monitor.IsEntered(SyncObj));

            // Dequeue an item.
            T item = _items.DequeueHead();

            // If we're now empty and we're done writing, complete the channel.
            if (_doneWriting != null && _items.IsEmpty)
            {
                ChannelUtilities.Complete(_completion, _doneWriting);
            }

            // If there are any writers blocked, there's now room for at least one
            // to be promoted to have its item moved into the items queue.  We need
            // to loop while trying to complete the writer in order to find one that
            // hasn't yet been canceled (canceled writers transition to canceled but
            // remain in the physical queue).
            while (!_blockedWriters.IsEmpty)
            {
                WriterInteractor <T> w = _blockedWriters.DequeueHead();
                if (w.Success(default))
Esempio n. 6
0
        private bool TryWrite(T item)
        {
            ReaderInteractor <T>    blockedReader  = null;
            ReaderInteractor <bool> waitingReaders = null;

            lock (SyncObj)
            {
                AssertInvariants();

                // If we're done writing, nothing more to do.
                if (_doneWriting != null)
                {
                    return(false);
                }

                // Get the number of items in the channel currently.
                int count = _items.Count;

                if (count == 0)
                {
                    // There are no items in the channel, which means we may have blocked/waiting readers.

                    // If there are any blocked readers, find one that's not canceled
                    // and store it to complete outside of the lock, in case it has
                    // continuations that'll run synchronously
                    while (!_blockedReaders.IsEmpty)
                    {
                        ReaderInteractor <T> r = _blockedReaders.DequeueHead();
                        r.UnregisterCancellation(); // ensure that once we grab it, we own its completion
                        if (!r.Task.IsCompleted)
                        {
                            blockedReader = r;
                            break;
                        }
                    }

                    if (blockedReader == null)
                    {
                        // If there wasn't a blocked reader, then store the item. If no one's waiting
                        // to be notified about a 0-to-1 transition, we're done.
                        _items.EnqueueTail(item);
                        waitingReaders = _waitingReaders;
                        if (waitingReaders == null)
                        {
                            return(true);
                        }
                        _waitingReaders = null;
                    }
                }
                else if (count < _bufferedCapacity)
                {
                    // There's room in the channel.  Since we're not transitioning from 0-to-1 and
                    // since there's room, we can simply store the item and exit without having to
                    // worry about blocked/waiting readers.
                    _items.EnqueueTail(item);
                    return(true);
                }
                else if (_mode == BoundedChannelFullMode.Wait)
                {
                    // The channel is full and we're in a wait mode.
                    // Simply exit and let the caller know we didn't write the data.
                    return(false);
                }
                else
                {
                    // The channel is full, and we're in a dropping mode.
                    // Drop either the oldest or the newest and write the new item.
                    T droppedItem = _mode == BoundedChannelFullMode.DropNewest ?
                                    _items.DequeueTail() :
                                    _items.DequeueHead();
                    _items.EnqueueTail(item);
                    return(true);
                }
            }

            // We either wrote the item already, or we're transfering it to the blocked reader we grabbed.
            if (blockedReader != null)
            {
                // Transfer the written item to the blocked reader.
                bool success = blockedReader.Success(item);
                Debug.Assert(success, "We should always be able to complete the reader.");
            }
            else
            {
                // We stored an item bringing the count up from 0 to 1.  Alert
                // any waiting readers that there may be something for them to consume.
                // Since we're no longer holding the lock, it's possible we'll end up
                // waking readers that have since come in.
                waitingReaders.Success(item: true);
            }

            return(true);
        }