Exemple #1
0
            public override ValueTask <T> ReadAsync(CancellationToken cancellationToken)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    return(new ValueTask <T>(Task.FromCanceled <T>(cancellationToken)));
                }

                BoundedChannel <T> parent = _parent;

                lock (parent.SyncObj)
                {
                    parent.AssertInvariants();

                    // If there are any items, hand one back.
                    if (!parent._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 (parent._doneWriting != null)
                    {
                        return(ChannelUtilities.GetInvalidCompletionValueTask <T>(parent._doneWriting));
                    }

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

                    parent._blockedReaders.EnqueueTail(reader);
                    return(new ValueTask <T>(reader.Task));
                }
            }
Exemple #2
0
            public override Task <bool> WaitToReadAsync(CancellationToken cancellationToken)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    return(Task.FromCanceled <bool>(cancellationToken));
                }

                UnbufferedChannel <T> parent = _parent;

                lock (parent.SyncObj)
                {
                    // If we're done writing, fail.
                    if (parent._completion.Task.IsCompleted)
                    {
                        return(parent._completion.Task.IsFaulted ?
                               Task.FromException <bool>(parent._completion.Task.Exception.InnerException) :
                               ChannelUtilities.s_falseTask);
                    }

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

                    // Otherwise, queue the waiter.
                    return(ChannelUtilities.GetOrCreateWaiter(ref parent._waitingReaders, true, cancellationToken));
                }
            }
Exemple #3
0
            /// <summary>Dequeues an item, and then fixes up our state around writers and completion.</summary>
            /// <returns>The dequeued item.</returns>
            private T DequeueItemAndPostProcess()
            {
                BoundedChannel <T> parent = _parent;

                Debug.Assert(Monitor.IsEntered(parent.SyncObj));

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

                if (parent._doneWriting != null)
                {
                    // We're done writing, so if we're now empty, complete the channel.
                    if (parent._items.IsEmpty)
                    {
                        ChannelUtilities.Complete(parent._completion, parent._doneWriting);
                    }
                }
                else
                {
                    // 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).
                    //
                    // (It's possible for _doneWriting to be non-null due to Complete
                    // having been called but for there to still be blocked/waiting writers.
                    // This is a temporary condition, after which Complete has set _doneWriting
                    // and then exited the lock; at that point it'll proceed to clean this up,
                    // so we just ignore them.)

                    while (!parent._blockedWriters.IsEmpty)
                    {
                        WriterInteractor <T> w = parent._blockedWriters.DequeueHead();
                        if (w.Success(default))
Exemple #4
0
        /// <summary>Asynchronously writes an item to the channel.</summary>
        /// <param name="item">The value to write to the channel.</param>
        /// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the write operation.</param>
        /// <returns>A <see cref="Task"/> that represents the asynchronous write operation.</returns>
        public virtual Task WriteAsync(T item, CancellationToken cancellationToken = default(CancellationToken))
        {
            try
            {
                return
                    (cancellationToken.IsCancellationRequested ? Task.FromCanceled <T>(cancellationToken) :
                     TryWrite(item) ? Task.CompletedTask :
                     WriteAsyncCore(item, cancellationToken));
            }
            catch (Exception e)
            {
                return(Task.FromException(e));
            }

            async Task WriteAsyncCore(T innerItem, CancellationToken ct)
            {
                while (await WaitToWriteAsync(ct).ConfigureAwait(false))
                {
                    if (TryWrite(innerItem))
                    {
                        return;
                    }
                }

                throw ChannelUtilities.CreateInvalidCompletionException();
            }
        }
Exemple #5
0
 public override bool TryRead(out T item)
 {
     SingleConsumerUnboundedChannel<T> parent = _parent;
     if (parent._items.TryDequeue(out item))
     {
         if (parent._doneWriting != null && parent._items.IsEmpty)
         {
             ChannelUtilities.Complete(parent._completion, parent._doneWriting);
         }
         return true;
     }
     return false;
 }
Exemple #6
0
            public override ValueTask <T> ReadAsync(CancellationToken cancellationToken)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    return(new ValueTask <T>(Task.FromCanceled <T>(cancellationToken)));
                }

                BoundedChannel <T> parent = _parent;

                lock (parent.SyncObj)
                {
                    parent.AssertInvariants();

                    // If there are any items, hand one back.
                    if (!parent._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 (parent._doneWriting != null)
                    {
                        return(ChannelUtilities.GetInvalidCompletionValueTask <T>(parent._doneWriting));
                    }

                    // If we're able to use the singleton reader, do so.
                    if (!cancellationToken.CanBeCanceled)
                    {
                        AsyncOperation <T> singleton = _readerSingleton;
                        if (singleton.TryOwnAndReset())
                        {
                            parent._blockedReaders.EnqueueTail(singleton);
                            return(singleton.ValueTaskOfT);
                        }
                    }

                    // Otherwise, queue a reader.  Note that in addition to checking whether synchronous continuations were requested,
                    // we also check whether the supplied cancellation token can be canceled.  The writer calls UnregisterCancellation
                    // while holding the lock, and if a callback needs to be unregistered and is currently running, it needs to wait
                    // for that callback to complete so that the subsequent code knows it won't be contending with another thread
                    // trying to complete the operation.  However, if we allowed a synchronous continuation from this operation, that
                    // cancellation callback could end up running arbitrary code, including code that called back into the reader or
                    // writer and tried to take the same lock held by the thread running UnregisterCancellation... deadlock.  As such,
                    // we only allow synchronous continuations here if both a) the caller requested it and the token isn't cancelable.
                    var reader = new AsyncOperation <T>(parent._runContinuationsAsynchronously | cancellationToken.CanBeCanceled, cancellationToken);
                    parent._blockedReaders.EnqueueTail(reader);
                    return(reader.ValueTaskOfT);
                }
            }
Exemple #7
0
            public override ValueTask <bool> WaitToReadAsync(CancellationToken cancellationToken)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    return(new ValueTask <bool>(Task.FromCanceled <bool>(cancellationToken)));
                }

                BoundedChannel <T> parent = _parent;

                lock (parent.SyncObj)
                {
                    parent.AssertInvariants();

                    // If there are any items available, a read is possible.
                    if (!parent._items.IsEmpty)
                    {
                        return(new ValueTask <bool>(true));
                    }

                    // There were no items available, so if we're done writing, a read will never be possible.
                    if (parent._doneWriting != null)
                    {
                        return(parent._doneWriting != ChannelUtilities.s_doneWritingSentinel ?
                               new ValueTask <bool>(Task.FromException <bool>(parent._doneWriting)) :
                               new ValueTask <bool>(false));
                    }

                    // There were no items available, but there could be in the future, so ensure
                    // there's a blocked reader task and return it.

                    // If we're able to use the singleton waiter, do so.
                    if (!cancellationToken.CanBeCanceled)
                    {
                        AsyncOperation <bool> singleton = _waiterSingleton;
                        if (singleton.TryOwnAndReset())
                        {
                            ChannelUtilities.QueueWaiter(ref parent._waitingReadersTail, singleton);
                            return(singleton.ValueTaskOfT);
                        }
                    }

                    // Otherwise, queue a reader.
                    var waiter = new AsyncOperation <bool>(parent._runContinuationsAsynchronously, cancellationToken);
                    ChannelUtilities.QueueWaiter(ref _parent._waitingReadersTail, waiter);
                    return(waiter.ValueTaskOfT);
                }
            }
Exemple #8
0
            public override ValueTask <T> ReadAsync(CancellationToken cancellationToken)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    return(new ValueTask <T>(Task.FromCanceled <T>(cancellationToken)));
                }

                BoundedChannel <T> parent = _parent;

                lock (parent.SyncObj)
                {
                    parent.AssertInvariants();

                    // If there are any items, hand one back.
                    if (!parent._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 (parent._doneWriting != null)
                    {
                        return(ChannelUtilities.GetInvalidCompletionValueTask <T>(parent._doneWriting));
                    }

                    // If we're able to use the singleton reader, do so.
                    if (!cancellationToken.CanBeCanceled)
                    {
                        AsyncOperation <T> singleton = _readerSingleton;
                        if (singleton.TryOwnAndReset())
                        {
                            parent._blockedReaders.EnqueueTail(singleton);
                            return(singleton.ValueTaskOfT);
                        }
                    }

                    // Otherwise, queue the reader.
                    var reader = new AsyncOperation <T>(parent._runContinuationsAsynchronously, cancellationToken);
                    parent._blockedReaders.EnqueueTail(reader);
                    return(reader.ValueTaskOfT);
                }
            }
            public override ValueTask <T> ReadAsync(CancellationToken cancellationToken)
            {
                {
                    return(TryRead(out T item) ?
                           new ValueTask <T>(item) :
                           ReadAsyncCore(cancellationToken));
                }

                ValueTask <T> ReadAsyncCore(CancellationToken ct)
                {
                    SingleConsumerUnboundedChannel <T> parent = _parent;

                    if (ct.IsCancellationRequested)
                    {
                        return(new ValueTask <T>(Task.FromCanceled <T>(ct)));
                    }

                    lock (parent.SyncObj)
                    {
                        // Now that we hold the lock, try reading again.
                        if (TryRead(out T item))
                        {
                            return(new ValueTask <T>(item));
                        }

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

                        Debug.Assert(parent._blockedReader == null || parent._blockedReader.Task.IsCanceled,
                                     "Incorrect usage; multiple outstanding reads were issued against this single-consumer channel");

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

                        parent._blockedReader = reader;
                        return(new ValueTask <T>(reader.Task));
                    }
                }
            }
Exemple #10
0
            /// <summary>Dequeues an item, and then fixes up our state around writers and completion.</summary>
            /// <returns>The dequeued item.</returns>
            private T DequeueItemAndPostProcess()
            {
                BoundedChannel <T> parent = _parent;

                Debug.Assert(Monitor.IsEntered(parent.SyncObj));

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

                // If we're now empty and we're done writing, complete the channel.
                if (parent._doneWriting != null && parent._items.IsEmpty)
                {
                    ChannelUtilities.Complete(parent._completion, parent._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 (!parent._blockedWriters.IsEmpty)
                {
                    WriterInteractor <T> w = parent._blockedWriters.DequeueHead();
                    if (w.Success(default))
Exemple #11
0
 public override ValueTask WriteAsync(T item, CancellationToken cancellationToken) =>
 // Writing always succeeds (unless we've already completed writing or cancellation has been requested),
 // so just TryWrite and return a completed task.
 cancellationToken.IsCancellationRequested ? new ValueTask(Task.FromCanceled(cancellationToken)) :
 TryWrite(item) ? default :
 new ValueTask(Task.FromException(ChannelUtilities.CreateInvalidCompletionException(_parent._doneWriting)));
Exemple #12
0
            public override bool TryComplete(Exception error)
            {
                AsyncOperation <T>    blockedReader = null;
                AsyncOperation <bool> waitingReader = null;
                bool completeTask = false;

                SingleConsumerUnboundedChannel <T> parent = _parent;

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

                    // Mark as complete for writing.
                    parent._doneWriting = error ?? ChannelUtilities.s_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 (parent._items.IsEmpty)
                    {
                        completeTask = true;

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

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

                // Complete the channel task if necessary
                if (completeTask)
                {
                    ChannelUtilities.Complete(parent._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);
                    blockedReader.TrySetException(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.TrySetException(error);
                    }
                    else
                    {
                        waitingReader.TrySetResult(item: false);
                    }
                }

                // Successfully completed the channel
                return(true);
            }