/// <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))
/// <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()); } }
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))
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); }