public override bool TryComplete(Exception?error) { BoundedChannel <T> parent = _parent; bool completeTask; lock (parent.SyncObj) { parent.AssertInvariants(); // If we've already marked the channel as completed, bail. if (parent._doneWriting != null) { return(false); } // Mark that we're done writing. parent._doneWriting = error ?? ChannelUtilities.s_doneWritingSentinel; completeTask = parent._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(parent._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 them without any concurrency concerns. ChannelUtilities.FailOperations <AsyncOperation <T>, T>(parent._blockedReaders, ChannelUtilities.CreateInvalidCompletionException(error)); ChannelUtilities.FailOperations <VoidAsyncOperationWithData <T>, VoidResult>(parent._blockedWriters, ChannelUtilities.CreateInvalidCompletionException(error)); ChannelUtilities.WakeUpWaiters(ref parent._waitingReadersTail, result: false, error: error); ChannelUtilities.WakeUpWaiters(ref parent._waitingWritersTail, result: false, error: error); // Successfully transitioned to completed. return(true); }
public override bool TryWrite(T item) { AsyncOperation <T>? blockedReader = null; AsyncOperation <bool>?waitingReadersTail = null; BoundedChannel <T> parent = _parent; lock (parent.SyncObj) { parent.AssertInvariants(); // If we're done writing, nothing more to do. if (parent._doneWriting != null) { return(false); } // Get the number of items in the channel currently. int count = parent._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 (!parent._blockedReaders.IsEmpty) { AsyncOperation <T> r = parent._blockedReaders.DequeueHead(); r.UnregisterCancellation(); // ensure that once we grab it, we own its completion if (!r.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. parent._items.EnqueueTail(item); waitingReadersTail = parent._waitingReadersTail; if (waitingReadersTail == null) { return(true); } parent._waitingReadersTail = null; } } else if (count < parent._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. parent._items.EnqueueTail(item); return(true); } else if (parent._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 if (parent._mode == BoundedChannelFullMode.DropWrite) { // The channel is full. Just ignore the item being added // but say we added it. return(true); } else { // The channel is full, and we're in a dropping mode. // Drop either the oldest or the newest and write the new item. if (parent._mode == BoundedChannelFullMode.DropNewest) { parent._items.DequeueTail(); } else { parent._items.DequeueHead(); } parent._items.EnqueueTail(item); return(true); } } // We either wrote the item already, or we're transferring it to the blocked reader we grabbed. if (blockedReader != null) { Debug.Assert(waitingReadersTail == null, "Shouldn't have any waiters to wake up"); // Transfer the written item to the blocked reader. bool success = blockedReader.TrySetResult(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. ChannelUtilities.WakeUpWaiters(ref waitingReadersTail, result: true); } return(true); }