public override ValueTask <T> ReadAsync(CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return(new ValueTask <T>(Task.FromCanceled <T>(cancellationToken))); } BoundedChannel <T> parent = _parent; lock (parent.SyncObj) { parent.AssertInvariants(); // If there are any items, hand one back. if (!parent._items.IsEmpty) { return(new ValueTask <T>(DequeueItemAndPostProcess())); } // There weren't any items. If we're done writing so that there // will never be more items, fail. if (parent._doneWriting != null) { return(ChannelUtilities.GetInvalidCompletionValueTask <T>(parent._doneWriting)); } // Otherwise, queue the reader. var reader = ReaderInteractor <T> .Create(parent._runContinuationsAsynchronously, cancellationToken); parent._blockedReaders.EnqueueTail(reader); return(new ValueTask <T>(reader.Task)); } }
public override Task <bool> WaitToReadAsync(CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return(Task.FromCanceled <bool>(cancellationToken)); } UnbufferedChannel <T> parent = _parent; lock (parent.SyncObj) { // If we're done writing, fail. if (parent._completion.Task.IsCompleted) { return(parent._completion.Task.IsFaulted ? Task.FromException <bool>(parent._completion.Task.Exception.InnerException) : ChannelUtilities.s_falseTask); } // If there's a blocked writer, we can read. if (!parent._blockedWriters.IsEmpty) { return(ChannelUtilities.s_trueTask); } // Otherwise, queue the waiter. return(ChannelUtilities.GetOrCreateWaiter(ref parent._waitingReaders, true, cancellationToken)); } }
/// <summary>Dequeues an item, and then fixes up our state around writers and completion.</summary> /// <returns>The dequeued item.</returns> private T DequeueItemAndPostProcess() { BoundedChannel <T> parent = _parent; Debug.Assert(Monitor.IsEntered(parent.SyncObj)); // Dequeue an item. T item = parent._items.DequeueHead(); if (parent._doneWriting != null) { // We're done writing, so if we're now empty, complete the channel. if (parent._items.IsEmpty) { ChannelUtilities.Complete(parent._completion, parent._doneWriting); } } else { // 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). // // (It's possible for _doneWriting to be non-null due to Complete // having been called but for there to still be blocked/waiting writers. // This is a temporary condition, after which Complete has set _doneWriting // and then exited the lock; at that point it'll proceed to clean this up, // so we just ignore them.) while (!parent._blockedWriters.IsEmpty) { WriterInteractor <T> w = parent._blockedWriters.DequeueHead(); if (w.Success(default))
/// <summary>Asynchronously writes an item to the channel.</summary> /// <param name="item">The value to write to the channel.</param> /// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the write operation.</param> /// <returns>A <see cref="Task"/> that represents the asynchronous write operation.</returns> public virtual Task WriteAsync(T item, CancellationToken cancellationToken = default(CancellationToken)) { try { return (cancellationToken.IsCancellationRequested ? Task.FromCanceled <T>(cancellationToken) : TryWrite(item) ? Task.CompletedTask : WriteAsyncCore(item, cancellationToken)); } catch (Exception e) { return(Task.FromException(e)); } async Task WriteAsyncCore(T innerItem, CancellationToken ct) { while (await WaitToWriteAsync(ct).ConfigureAwait(false)) { if (TryWrite(innerItem)) { return; } } throw ChannelUtilities.CreateInvalidCompletionException(); } }
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) { if (cancellationToken.IsCancellationRequested) { return(new ValueTask <T>(Task.FromCanceled <T>(cancellationToken))); } BoundedChannel <T> parent = _parent; lock (parent.SyncObj) { parent.AssertInvariants(); // If there are any items, hand one back. if (!parent._items.IsEmpty) { return(new ValueTask <T>(DequeueItemAndPostProcess())); } // There weren't any items. If we're done writing so that there // will never be more items, fail. if (parent._doneWriting != null) { return(ChannelUtilities.GetInvalidCompletionValueTask <T>(parent._doneWriting)); } // If we're able to use the singleton reader, do so. if (!cancellationToken.CanBeCanceled) { AsyncOperation <T> singleton = _readerSingleton; if (singleton.TryOwnAndReset()) { parent._blockedReaders.EnqueueTail(singleton); return(singleton.ValueTaskOfT); } } // Otherwise, queue a reader. Note that in addition to checking whether synchronous continuations were requested, // we also check whether the supplied cancellation token can be canceled. The writer calls UnregisterCancellation // while holding the lock, and if a callback needs to be unregistered and is currently running, it needs to wait // for that callback to complete so that the subsequent code knows it won't be contending with another thread // trying to complete the operation. However, if we allowed a synchronous continuation from this operation, that // cancellation callback could end up running arbitrary code, including code that called back into the reader or // writer and tried to take the same lock held by the thread running UnregisterCancellation... deadlock. As such, // we only allow synchronous continuations here if both a) the caller requested it and the token isn't cancelable. var reader = new AsyncOperation <T>(parent._runContinuationsAsynchronously | cancellationToken.CanBeCanceled, cancellationToken); parent._blockedReaders.EnqueueTail(reader); return(reader.ValueTaskOfT); } }
public override ValueTask <bool> WaitToReadAsync(CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return(new ValueTask <bool>(Task.FromCanceled <bool>(cancellationToken))); } BoundedChannel <T> parent = _parent; lock (parent.SyncObj) { parent.AssertInvariants(); // If there are any items available, a read is possible. if (!parent._items.IsEmpty) { return(new ValueTask <bool>(true)); } // There were no items available, so if we're done writing, a read will never be possible. if (parent._doneWriting != null) { return(parent._doneWriting != ChannelUtilities.s_doneWritingSentinel ? new ValueTask <bool>(Task.FromException <bool>(parent._doneWriting)) : new ValueTask <bool>(false)); } // There were no items available, but there could be in the future, so ensure // there's a blocked reader task and return it. // If we're able to use the singleton waiter, do so. if (!cancellationToken.CanBeCanceled) { AsyncOperation <bool> singleton = _waiterSingleton; if (singleton.TryOwnAndReset()) { ChannelUtilities.QueueWaiter(ref parent._waitingReadersTail, singleton); return(singleton.ValueTaskOfT); } } // Otherwise, queue a reader. var waiter = new AsyncOperation <bool>(parent._runContinuationsAsynchronously, cancellationToken); ChannelUtilities.QueueWaiter(ref _parent._waitingReadersTail, waiter); return(waiter.ValueTaskOfT); } }
public override ValueTask <T> ReadAsync(CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return(new ValueTask <T>(Task.FromCanceled <T>(cancellationToken))); } BoundedChannel <T> parent = _parent; lock (parent.SyncObj) { parent.AssertInvariants(); // If there are any items, hand one back. if (!parent._items.IsEmpty) { return(new ValueTask <T>(DequeueItemAndPostProcess())); } // There weren't any items. If we're done writing so that there // will never be more items, fail. if (parent._doneWriting != null) { return(ChannelUtilities.GetInvalidCompletionValueTask <T>(parent._doneWriting)); } // If we're able to use the singleton reader, do so. if (!cancellationToken.CanBeCanceled) { AsyncOperation <T> singleton = _readerSingleton; if (singleton.TryOwnAndReset()) { parent._blockedReaders.EnqueueTail(singleton); return(singleton.ValueTaskOfT); } } // Otherwise, queue the reader. var reader = new AsyncOperation <T>(parent._runContinuationsAsynchronously, cancellationToken); parent._blockedReaders.EnqueueTail(reader); return(reader.ValueTaskOfT); } }
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)); } } }
/// <summary>Dequeues an item, and then fixes up our state around writers and completion.</summary> /// <returns>The dequeued item.</returns> private T DequeueItemAndPostProcess() { BoundedChannel <T> parent = _parent; Debug.Assert(Monitor.IsEntered(parent.SyncObj)); // Dequeue an item. T item = parent._items.DequeueHead(); // If we're now empty and we're done writing, complete the channel. if (parent._doneWriting != null && parent._items.IsEmpty) { ChannelUtilities.Complete(parent._completion, parent._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 (!parent._blockedWriters.IsEmpty) { WriterInteractor <T> w = parent._blockedWriters.DequeueHead(); if (w.Success(default))
public override ValueTask WriteAsync(T item, CancellationToken cancellationToken) => // Writing always succeeds (unless we've already completed writing or cancellation has been requested), // so just TryWrite and return a completed task. cancellationToken.IsCancellationRequested ? new ValueTask(Task.FromCanceled(cancellationToken)) : TryWrite(item) ? default : new ValueTask(Task.FromException(ChannelUtilities.CreateInvalidCompletionException(_parent._doneWriting)));
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); }