public override bool TryWrite(T item) { UnboundedChannel <T> parent = _parent; while (true) { AsyncOperation <T> blockedReader = null; AsyncOperation <bool> waitingReadersTail = null; lock (parent.SyncObj) { // If writing has already been marked as done, fail the write. parent.AssertInvariants(); if (parent._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 (parent._blockedReaders.IsEmpty) { parent._items.Enqueue(item); waitingReadersTail = parent._waitingReadersTail; if (waitingReadersTail == null) { return(true); } parent._waitingReadersTail = null; } else { // There were blocked readers. Grab one, and then complete it outside of the lock. blockedReader = parent._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.TrySetResult(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. ChannelUtilities.WakeUpWaiters(ref waitingReadersTail, result: true); return(true); } } }
public override bool TryWrite(T item) { SingleConsumerUnboundedChannel <T> parent = _parent; while (true) // in case a reader was canceled and we need to try again { AsyncOperation <T> blockedReader = null; AsyncOperation <bool> waitingReader = null; lock (parent.SyncObj) { // If writing is completed, exit out without writing. if (parent._doneWriting != null) { return(false); } // If there's a blocked reader, store it into a local for completion outside of the lock. // If there isn't a blocked reader, queue the item being written; then if there's a waiting blockedReader = parent._blockedReader; if (blockedReader != null) { parent._blockedReader = null; } else { parent._items.Enqueue(item); waitingReader = parent._waitingReader; if (waitingReader == null) { return(true); } parent._waitingReader = null; } } // If we get here, we grabbed a blocked or a waiting reader. Debug.Assert((blockedReader != null) ^ (waitingReader != null), "Expected either a blocked or waiting reader, but not both"); // If we have a waiting reader, notify it that an item was written and exit. if (waitingReader != null) { // If we get here, we grabbed a waiting reader. waitingReader.TrySetResult(item: true); return(true); } // Otherwise we have a blocked reader: complete it with the item being written. // In the case of a ReadAsync(CancellationToken), it's possible the reader could // have been completed due to cancellation by the time we get here. In that case, // we'll loop around to try again so as not to lose the item being written. Debug.Assert(blockedReader != null); if (blockedReader.TrySetResult(item)) { return(true); } } }
internal static void WakeUpWaiters(ref AsyncOperation <bool>?listTail, bool result, Exception?error = null) { AsyncOperation <bool>?tail = listTail; if (tail != null) { listTail = null; AsyncOperation <bool> head = tail.Next !; AsyncOperation <bool> c = head; do { AsyncOperation <bool> next = c.Next !; c.Next = null; bool completed = error != null?c.TrySetException(error) : c.TrySetResult(result); Debug.Assert(completed || c.CancellationToken.CanBeCanceled); c = next; }while (c != head); } }
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); }