Beispiel #1
0
        private Task <bool> WaitToReadAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            // If there are any items, readers can try to get them.
            if (!_items.IsEmpty)
            {
                return(ChannelUtilities.TrueTask);
            }

            lock (SyncObj)
            {
                AssertInvariants();

                // Try again to read now that we're synchronized with writers.
                if (!_items.IsEmpty)
                {
                    return(ChannelUtilities.TrueTask);
                }

                // There are no items, so if we're done writing, there's never going to be data available.
                if (_doneWriting != null)
                {
                    return(_doneWriting != ChannelUtilities.DoneWritingSentinel ?
                           Task.FromException <bool>(_doneWriting) :
                           ChannelUtilities.FalseTask);
                }

                // Queue the waiter
                return(ChannelUtilities.GetOrCreateWaiter(ref _waitingReaders, _runContinuationsAsynchronously, cancellationToken));
            }
        }
        private ValueAwaiter <T> GetAwaiterCore()
        {
            lock (SyncObj)
            {
                // Now that we hold the lock, try reading again.
                T item;
                if (TryRead(out item))
                {
                    return(new ValueAwaiter <T>(item));
                }

                // If no more items will be written, fail the read.
                if (_doneWriting != null)
                {
                    return(new ValueAwaiter <T>(ChannelUtilities.GetInvalidCompletionValueTask <T>(_doneWriting)));
                }

                Debug.Assert(_blockedReader == null || ((_blockedReader as ReaderInteractor <T>)?.Task.IsCanceled ?? false),
                             "Incorrect usage; multiple outstanding reads were issued against this single-consumer channel");

                // Store the reader to be completed by a writer.
                _blockedReader = _awaiter ?? (_awaiter = new AutoResetAwaiter <T>(_runContinuationsAsynchronously));
                return(new ValueAwaiter <T>(_awaiter));
            }
        }
Beispiel #3
0
        private ValueTask <T> ReadAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            // Fast-path cancellation check
            if (cancellationToken.IsCancellationRequested)
            {
                return(new ValueTask <T>(Task.FromCanceled <T>(cancellationToken)));
            }

            lock (SyncObj)
            {
                AssertInvariants();

                // If there are any items, hand one back.
                if (!_items.IsEmpty)
                {
                    return(new ValueTask <T>(DequeueItemAndPostProcess()));
                }

                // There weren't any items.  If we're done writing so that there
                // will never be more items, fail.
                if (_doneWriting != null)
                {
                    return(ChannelUtilities.GetErrorValueTask <T>(_doneWriting));
                }

                // Otherwise, queue the reader.
                var reader = ReaderInteractor <T> .Create(_runContinuationsAsynchronously, cancellationToken);

                _blockedReaders.EnqueueTail(reader);
                return(new ValueTask <T>(reader.Task));
            }
        }
Beispiel #4
0
        private Task <bool> WaitToReadAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            if (cancellationToken.IsCancellationRequested)
            {
                return(Task.FromCanceled <bool>(cancellationToken));
            }

            lock (SyncObj)
            {
                AssertInvariants();

                // If there are any items available, a read is possible.
                if (!_items.IsEmpty)
                {
                    return(ChannelUtilities.TrueTask);
                }

                // There were no items available, so if we're done writing, a read will never be possible.
                if (_doneWriting != null)
                {
                    return(ChannelUtilities.FalseTask);
                }

                // There were no items available, but there could be in the future, so ensure
                // there's a blocked reader task and return it.
                return(ChannelUtilities.GetOrCreateWaiter(ref _waitingReaders, _runContinuationsAsynchronously, cancellationToken));
            }
        }
        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))
Beispiel #6
0
        private Task <bool> WaitToWriteAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            if (cancellationToken.IsCancellationRequested)
            {
                return(Task.FromCanceled <bool>(cancellationToken));
            }

            lock (SyncObj)
            {
                AssertInvariants();

                // If we're done writing, no writes will ever succeed.
                if (_doneWriting != null)
                {
                    return(ChannelUtilities.FalseTask);
                }

                // If there's space to write, a write is possible.
                // And if the mode involves dropping, we can always write, as even if it's
                // full we'll just drop an element to make room.
                if (_items.Count < _bufferedCapacity || _mode != BoundedChannelFullMode.Wait)
                {
                    return(ChannelUtilities.TrueTask);
                }

                // We're still allowed to write, but there's no space, so ensure a waiter is queued and return it.
                return(ChannelUtilities.GetOrCreateWaiter(ref _waitingWriters, true, cancellationToken));
            }
        }
Beispiel #7
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)
            {
                // Now that we hold the lock, try reading again.
                T item;
                if (TryRead(out item))
                {
                    return(new ValueTask <T>(item));
                }

                // If no more items will be written, fail the read.
                if (_doneWriting != null)
                {
                    return(ChannelUtilities.GetInvalidCompletionValueTask <T>(_doneWriting));
                }

                Debug.Assert(_blockedReader == null || ((_blockedReader as ReaderInteractor <T>)?.Task.IsCanceled ?? false),
                             "Incorrect usage; multiple outstanding reads were issued against this single-consumer channel");

                // Store the reader to be completed by a writer.
                ReaderInteractor <T> reader = ReaderInteractor <T> .Create(_runContinuationsAsynchronously, cancellationToken);

                _blockedReader = reader;
                return(new ValueTask <T>(reader.Task));
            }
        }
 /// <summary>Mark the channel as being complete, meaning no more items will be written to it.</summary>
 /// <param name="error">Optional Exception indicating a failure that's causing the channel to complete.</param>
 /// <exception cref="InvalidOperationException">The channel has already been marked as complete.</exception>
 public void Complete(Exception error = null)
 {
     if (!TryComplete(error))
     {
         throw ChannelUtilities.CreateInvalidCompletionException();
     }
 }
 private Task WriteAsync(T item, CancellationToken cancellationToken = default)
 {
     // Writing always succeeds (unless we've already completed writing or cancellation has been requested),
     // so just TryWrite and return a completed task.
     return
         (cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) :
          TryWrite(item) ? Task.CompletedTask :
          Task.FromException(ChannelUtilities.CreateInvalidCompletionException(_doneWriting)));
 }
 private bool TryRead(out T item)
 {
     if (_items.TryDequeue(out item))
     {
         if (_doneWriting != null && _items.IsEmpty)
         {
             ChannelUtilities.Complete(_completion, _doneWriting);
         }
         return(true);
     }
     return(false);
 }
Beispiel #12
0
        private bool TryRead(out T item)
        {
            // Dequeue an item if we can
            if (_items.TryDequeue(out item))
            {
                if (_doneWriting != null && _items.IsEmpty)
                {
                    // If we've now emptied the items queue and we're not getting any more, complete.
                    ChannelUtilities.Complete(_completion, _doneWriting);
                }
                return(true);
            }

            item = default(T);
            return(false);
        }
Beispiel #13
0
        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(VoidResult)))
                    {
                        return(new ValueTask <T>(w.Item));
                    }
                }

                // No writer found to pair with.  Queue the reader.
                var r = ReaderInteractor <T> .Create(true, cancellationToken);

                _blockedReaders.EnqueueTail(r);

                // And let any waiting writers know it's their lucky day.
                ChannelUtilities.WakeUpWaiters(ref _waitingWriters, result: true);

                return(new ValueTask <T>(r.Task));
            }
        }
Beispiel #14
0
        private Task WriteAsync(T item, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (cancellationToken.IsCancellationRequested)
            {
                return(Task.FromCanceled(cancellationToken));
            }

            lock (SyncObj)
            {
                // Fail if we've already completed
                if (_completion.Task.IsCompleted)
                {
                    return
                        (_completion.Task.IsCanceled ? Task.FromCanceled <T>(new CancellationToken(true)) :
                         Task.FromException <T>(
                             _completion.Task.IsFaulted ?
                             ChannelUtilities.CreateInvalidCompletionException(_completion.Task.Exception.InnerException) :
                             ChannelUtilities.CreateInvalidCompletionException()));
                }

                // Try to find a reader to pair with.  Canceled readers remain in the queue,
                // so we need to loop until we find one.
                while (!_blockedReaders.IsEmpty)
                {
                    ReaderInteractor <T> r = _blockedReaders.DequeueHead();
                    if (r.Success(item))
                    {
                        return(Task.CompletedTask);
                    }
                }

                // No reader was available.  Queue the writer.
                var w = WriterInteractor <T> .Create(true, cancellationToken, item);

                _blockedWriters.EnqueueTail(w);

                // And let any waiting readers know it's their lucky day.
                ChannelUtilities.WakeUpWaiters(ref _waitingReaders, result: true);

                return(w.Task);
            }
        }
Beispiel #15
0
        private bool TryComplete(Exception error = null)
        {
            bool completeTask;

            lock (SyncObj)
            {
                AssertInvariants();

                // If we've already marked the channel as completed, bail.
                if (_doneWriting != null)
                {
                    return(false);
                }

                // Mark that we're done writing.
                _doneWriting = error ?? ChannelUtilities.DoneWritingSentinel;
                completeTask = _items.IsEmpty;
            }

            // If there are no items in the queue, complete the channel's task,
            // as no more data can possibly arrive at this point.  We do this outside
            // of the lock in case we'll be running synchronous completions, and we
            // do it before completing blocked/waiting readers, so that when they
            // wake up they'll see the task as being completed.
            if (completeTask)
            {
                ChannelUtilities.Complete(_completion, error);
            }

            // At this point, _blockedReaders/Writers and _waitingReaders/Writers will not be mutated:
            // they're only mutated by readers/writers while holding the lock, and only if _doneWriting is null.
            // We also know that only one thread (this one) will ever get here, as only that thread
            // will be the one to transition from _doneWriting false to true.  As such, we can
            // freely manipulate _blockedReaders and _waitingReaders without any concurrency concerns.
            ChannelUtilities.FailInteractors <ReaderInteractor <T>, T>(_blockedReaders, error);
            ChannelUtilities.FailInteractors <WriterInteractor <T>, VoidResult>(_blockedWriters, error);
            ChannelUtilities.WakeUpWaiters(ref _waitingReaders, result: false);
            ChannelUtilities.WakeUpWaiters(ref _waitingWriters, result: false);

            // Successfully transitioned to completed.
            return(true);
        }
        private Task <bool> WaitToWriteAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            lock (SyncObj)
            {
                // If we're done writing, fail.
                if (_completion.Task.IsCompleted)
                {
                    return(ChannelUtilities.FalseTask);
                }

                // If there's a blocked reader, we can write
                if (!_blockedReaders.IsEmpty)
                {
                    return(ChannelUtilities.TrueTask);
                }

                // Otherwise, queue the writer
                return(ChannelUtilities.GetOrCreateWaiter(ref _waitingWriters, true, cancellationToken));
            }
        }
        /// <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))
Beispiel #18
0
        private Task <bool> WaitToReadAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            lock (SyncObj)
            {
                // If we're done writing, fail.
                if (_completion.Task.IsCompleted)
                {
                    return(_completion.Task.IsFaulted ?
                           Task.FromException <bool>(_completion.Task.Exception.InnerException) :
                           ChannelUtilities.FalseTask);
                }

                // If there's a blocked writer, we can read.
                if (!_blockedWriters.IsEmpty)
                {
                    return(ChannelUtilities.TrueTask);
                }

                // Otherwise, queue the waiter.
                return(ChannelUtilities.GetOrCreateWaiter(ref _waitingReaders, true, cancellationToken));
            }
        }
Beispiel #19
0
        private ValueTask <T> ReadAsyncCore(CancellationToken cancellationToken)
        {
            if (cancellationToken.IsCancellationRequested)
            {
                return(new ValueTask <T>(Task.FromCanceled <T>(cancellationToken)));
            }

            lock (SyncObj)
            {
                AssertInvariants();

                // If there are any items, return one.
                T item;
                if (_items.TryDequeue(out item))
                {
                    // Dequeue an item
                    if (_doneWriting != null && _items.IsEmpty)
                    {
                        // If we've now emptied the items queue and we're not getting any more, complete.
                        ChannelUtilities.Complete(_completion, _doneWriting);
                    }

                    return(new ValueTask <T>(item));
                }

                // There are no items, so if we're done writing, fail.
                if (_doneWriting != null)
                {
                    return(ChannelUtilities.GetInvalidCompletionValueTask <T>(_doneWriting));
                }

                // Otherwise, queue the reader.
                ReaderInteractor <T> reader = ReaderInteractor <T> .Create(_runContinuationsAsynchronously, cancellationToken);

                _blockedReaders.EnqueueTail(reader);
                return(new ValueTask <T>(reader.Task));
            }
        }
        private bool TryComplete(Exception error = null)
        {
            lock (SyncObj)
            {
                AssertInvariants();

                // Mark the channel as being done. Since there's no buffered data, we can complete immediately.
                if (_completion.Task.IsCompleted)
                {
                    return(false);
                }
                ChannelUtilities.Complete(_completion, error);

                // Fail any blocked readers/writers, as there will be no writers/readers to pair them with.
                ChannelUtilities.FailInteractors <ReaderInteractor <T>, T>(_blockedReaders, ChannelUtilities.CreateInvalidCompletionException(error));
                ChannelUtilities.FailInteractors <WriterInteractor <T>, VoidResult>(_blockedWriters, ChannelUtilities.CreateInvalidCompletionException(error));

                // Let any waiting readers and writers know there won't be any more data
                ChannelUtilities.WakeUpWaiters(ref _waitingReaders, result: false, error: error);
                ChannelUtilities.WakeUpWaiters(ref _waitingWriters, result: false, error: error);
            }

            return(true);
        }
Beispiel #21
0
        private Task WriteAsync(T item, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (cancellationToken.IsCancellationRequested)
            {
                return(Task.FromCanceled(cancellationToken));
            }

            ReaderInteractor <T>    blockedReader  = null;
            ReaderInteractor <bool> waitingReaders = null;

            lock (SyncObj)
            {
                AssertInvariants();

                // If we're done writing, trying to write is an error.
                if (_doneWriting != null)
                {
                    return(Task.FromException(ChannelUtilities.CreateInvalidCompletionException()));
                }

                // 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(ChannelUtilities.TrueTask);
                        }
                        _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(ChannelUtilities.TrueTask);
                }
                else if (_mode == BoundedChannelFullMode.Wait)
                {
                    // The channel is full and we're in a wait mode.
                    // Queue the writer.
                    var writer = WriterInteractor <T> .Create(true, cancellationToken, item);

                    _blockedWriters.EnqueueTail(writer);
                    return(writer.Task);
                }
                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(ChannelUtilities.TrueTask);
                }
            }

            // 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(ChannelUtilities.TrueTask);
        }
        private bool TryComplete(Exception error = null)
        {
            object blockedReader = null;
            ReaderInteractor <bool> waitingReader = null;
            bool completeTask = false;

            lock (SyncObj)
            {
                // If we're already marked as complete, there's nothing more to do.
                if (_doneWriting != null)
                {
                    return(false);
                }

                // Mark as complete for writing.
                _doneWriting = error ?? ChannelUtilities.DoneWritingSentinel;

                // If we have no more items remaining, then the channel needs to be marked as completed
                // and readers need to be informed they'll never get another item.  All of that needs
                // to happen outside of the lock to avoid invoking continuations under the lock.
                if (_items.IsEmpty)
                {
                    completeTask = true;

                    if (_blockedReader != null)
                    {
                        blockedReader  = _blockedReader;
                        _blockedReader = null;
                    }

                    if (_waitingReader != null)
                    {
                        waitingReader  = _waitingReader;
                        _waitingReader = null;
                    }
                }
            }

            // Complete the channel task if necessary
            if (completeTask)
            {
                ChannelUtilities.Complete(_completion, error);
            }

            Debug.Assert(blockedReader == null || waitingReader == null, "There should only ever be at most one reader.");

            // Complete a blocked reader if necessary
            if (blockedReader != null)
            {
                error = ChannelUtilities.CreateInvalidCompletionException(error);

                ReaderInteractor <T> interactor = blockedReader as ReaderInteractor <T>;
                if (interactor != null)
                {
                    interactor.Fail(error);
                }
                else
                {
                    ((AutoResetAwaiter <T>)blockedReader).SetException(error);
                }
            }

            // Complete a waiting reader if necessary.  (We really shouldn't have both a blockedReader
            // and a waitingReader, but it's more expensive to prevent it than to just tolerate it.)
            if (waitingReader != null)
            {
                if (error != null)
                {
                    waitingReader.Fail(error);
                }
                else
                {
                    waitingReader.Success(false);
                }
            }

            // Successfully completed the channel
            return(true);
        }
Beispiel #23
0
 private Task WriteAsync(T item, CancellationToken cancellationToken = default(CancellationToken)) =>
 cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) :
 TryWrite(item) ? Task.CompletedTask :
 Task.FromException(ChannelUtilities.CreateInvalidCompletionException(_doneWriting));