public override ValueTask <bool> WaitToReadAsync(CancellationToken cancellationToken) { // Outside of the lock, check if there are any items waiting to be read. If there are, we're done. if (cancellationToken.IsCancellationRequested) { return(new ValueTask <bool>(Task.FromCanceled <bool>(cancellationToken))); } if (!_parent._items.IsEmpty) { return(new ValueTask <bool>(true)); } SingleConsumerUnboundedChannel <T> parent = _parent; AsyncOperation <bool> oldWaitingReader = null, newWaitingReader; lock (parent.SyncObj) { // Again while holding the lock, check to see if there are any items available. if (!parent._items.IsEmpty) { return(new ValueTask <bool>(true)); } // There aren't any items; if we're done writing, there never will be more items. if (parent._doneWriting != null) { return(parent._doneWriting != ChannelUtilities.s_doneWritingSentinel ? new ValueTask <bool>(Task.FromException <bool>(parent._doneWriting)) :
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); } } }
public override ValueTask <bool> WaitToReadAsync(CancellationToken cancellationToken) { // Outside of the lock, check if there are any items waiting to be read. If there are, we're done. if (cancellationToken.IsCancellationRequested) { return(new ValueTask <bool>(Task.FromCanceled <bool>(cancellationToken))); } if (!_parent._items.IsEmpty) { return(new ValueTask <bool>(true)); } SingleConsumerUnboundedChannel <T> parent = _parent; AsyncOperation <bool> oldWaitingReader = null, newWaitingReader; lock (parent.SyncObj) { // Again while holding the lock, check to see if there are any items available. if (!parent._items.IsEmpty) { return(new ValueTask <bool>(true)); } // There aren't any items; if we're done writing, there never will be more items. if (parent._doneWriting != null) { return(parent._doneWriting != ChannelUtilities.s_doneWritingSentinel ? new ValueTask <bool>(Task.FromException <bool>(parent._doneWriting)) : new ValueTask <bool>(false)); } // Try to use the singleton waiter. If it's currently being used, then the channel // is being used erroneously, and we cancel the outstanding operation. oldWaitingReader = parent._waitingReader; if (!cancellationToken.CanBeCanceled && _waiterSingleton.TryOwnAndReset()) { newWaitingReader = _waiterSingleton; if (newWaitingReader == oldWaitingReader) { // The previous operation completed, so null out the "old" waiter // so we don't end up canceling the new operation. oldWaitingReader = null; } } else { newWaitingReader = new AsyncOperation <bool>(_parent._runContinuationsAsynchronously, cancellationToken); } parent._waitingReader = newWaitingReader; } oldWaitingReader?.TrySetCanceled(); return(newWaitingReader.ValueTaskOfT); }
public override ValueTask <T> ReadAsync(CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return(new ValueTask <T>(Task.FromCanceled <T>(cancellationToken))); } if (TryRead(out T item)) { return(new ValueTask <T>(item)); } SingleConsumerUnboundedChannel <T> parent = _parent; AsyncOperation <T> oldBlockedReader, newBlockedReader; lock (parent.SyncObj) { // Now that we hold the lock, try reading again. if (TryRead(out 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)); } // Try to use the singleton reader. If it's currently being used, then the channel // is being used erroneously, and we cancel the outstanding operation. oldBlockedReader = parent._blockedReader; if (!cancellationToken.CanBeCanceled && _readerSingleton.TryOwnAndReset()) { newBlockedReader = _readerSingleton; if (newBlockedReader == oldBlockedReader) { // The previous operation completed, so null out the "old" reader // so we don't end up canceling the new operation. oldBlockedReader = null; } } else { newBlockedReader = new AsyncOperation <T>(_parent._runContinuationsAsynchronously, cancellationToken); } parent._blockedReader = newBlockedReader; } oldBlockedReader?.TrySetCanceled(); return(newBlockedReader.ValueTaskOfT); }
internal UnboundedChannelReader(SingleConsumerUnboundedChannel <T> parent) { _parent = parent; _readerSingleton = new AsyncOperation <T>(parent._runContinuationsAsynchronously) { UnsafeState = ResettableValueTaskSource.States.Released }; _waiterSingleton = new AsyncOperation <bool>(parent._runContinuationsAsynchronously) { UnsafeState = ResettableValueTaskSource.States.Released }; }
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; }
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)); } } }
public override Task <bool> WaitToReadAsync(CancellationToken cancellationToken) { // Outside of the lock, check if there are any items waiting to be read. If there are, we're done. return (cancellationToken.IsCancellationRequested ? Task.FromCanceled <bool>(cancellationToken) : !_parent._items.IsEmpty ? ChannelUtilities.s_trueTask : WaitToReadAsyncCore(cancellationToken)); Task <bool> WaitToReadAsyncCore(CancellationToken ct) { SingleConsumerUnboundedChannel <T> parent = _parent; ReaderInteractor <bool> oldWaiter = null, newWaiter; lock (parent.SyncObj) { // Again while holding the lock, check to see if there are any items available. if (!parent._items.IsEmpty) { return(ChannelUtilities.s_trueTask); } // There aren't any items; if we're done writing, there never will be more items. if (parent._doneWriting != null) { return(parent._doneWriting != ChannelUtilities.s_doneWritingSentinel ? Task.FromException <bool>(parent._doneWriting) : ChannelUtilities.s_falseTask); } // Create the new waiter. We're a bit more tolerant of a stray waiting reader // than we are of a blocked reader, as with usage patterns it's easier to leave one // behind, so we just cancel any that may have been waiting around. oldWaiter = parent._waitingReader; parent._waitingReader = newWaiter = ReaderInteractor <bool> .Create(parent._runContinuationsAsynchronously, ct); } oldWaiter?.TrySetCanceled(); return(newWaiter.Task); } }
internal UnboundedChannelReader(SingleConsumerUnboundedChannel <T> parent) { _parent = parent; _readerSingleton = new AsyncOperation <T>(parent._runContinuationsAsynchronously, pooled: true); _waiterSingleton = new AsyncOperation <bool>(parent._runContinuationsAsynchronously, pooled: true); }
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); }
internal UnboundedChannelWriter(SingleConsumerUnboundedChannel <T> parent) => _parent = parent;