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