/// <summary>Dequeues an item, and then fixes up our state around writers and completion.</summary> /// <returns>The dequeued item.</returns> private T DequeueItemAndPostProcess() { Debug.Assert(Monitor.IsEntered(SyncObj)); // Dequeue an item. T item = _items.DequeueHead(); // If we're now empty and we're done writing, complete the channel. if (_doneWriting != null && _items.IsEmpty) { ChannelUtilities.Complete(_completion, _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 (!_blockedWriters.IsEmpty) { WriterInteractor <T> w = _blockedWriters.DequeueHead(); if (w.Success(default(VoidResult))) { _items.EnqueueTail(w.Item); return(item); } } // There was no blocked writer, so see if there's a WaitToWriteAsync // we should wake up. ChannelUtilities.WakeUpWaiters(ref _waitingWriters, result: true); // Return the item return(item); }
private bool TryRead(out T item) { if (_items.TryDequeue(out item)) { if (_doneWriting != null && _items.IsEmpty) { ChannelUtilities.Complete(_completion, _doneWriting); } return(true); } return(false); }
private bool TryRead(out T item) { // Dequeue an item if we can if (_items.TryDequeue(out item)) { if (_doneWriting != null && _items.IsEmpty) { // If we've now emptied the items queue and we're not getting any more, complete. ChannelUtilities.Complete(_completion, _doneWriting); } return(true); } item = default(T); return(false); }
private bool TryComplete(Exception error = null) { bool completeTask; lock (SyncObj) { AssertInvariants(); // If we've already marked the channel as completed, bail. if (_doneWriting != null) { return(false); } // Mark that we're done writing. _doneWriting = error ?? ChannelUtilities.DoneWritingSentinel; completeTask = _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(_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 _blockedReaders and _waitingReaders without any concurrency concerns. ChannelUtilities.FailInteractors <ReaderInteractor <T>, T>(_blockedReaders, error); ChannelUtilities.FailInteractors <WriterInteractor <T>, VoidResult>(_blockedWriters, error); ChannelUtilities.WakeUpWaiters(ref _waitingReaders, result: false); ChannelUtilities.WakeUpWaiters(ref _waitingWriters, result: false); // Successfully transitioned to completed. return(true); }
/// <summary>Dequeues an item, and then fixes up our state around writers and completion.</summary> /// <returns>The dequeued item.</returns> private T DequeueItemAndPostProcess() { Debug.Assert(Monitor.IsEntered(SyncObj)); // Dequeue an item. T item = _items.DequeueHead(); // If we're now empty and we're done writing, complete the channel. if (_doneWriting != null && _items.IsEmpty) { ChannelUtilities.Complete(_completion, _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 (!_blockedWriters.IsEmpty) { WriterInteractor <T> w = _blockedWriters.DequeueHead(); if (w.Success(default))
private ValueTask <T> ReadAsyncCore(CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return(new ValueTask <T>(Task.FromCanceled <T>(cancellationToken))); } lock (SyncObj) { AssertInvariants(); // If there are any items, return one. T item; if (_items.TryDequeue(out item)) { // Dequeue an item if (_doneWriting != null && _items.IsEmpty) { // If we've now emptied the items queue and we're not getting any more, complete. ChannelUtilities.Complete(_completion, _doneWriting); } return(new ValueTask <T>(item)); } // There are no items, so if we're done writing, fail. if (_doneWriting != null) { return(ChannelUtilities.GetInvalidCompletionValueTask <T>(_doneWriting)); } // Otherwise, queue the reader. ReaderInteractor <T> reader = ReaderInteractor <T> .Create(_runContinuationsAsynchronously, cancellationToken); _blockedReaders.EnqueueTail(reader); return(new ValueTask <T>(reader.Task)); } }
private bool TryComplete(Exception error = null) { lock (SyncObj) { AssertInvariants(); // Mark the channel as being done. Since there's no buffered data, we can complete immediately. if (_completion.Task.IsCompleted) { return(false); } ChannelUtilities.Complete(_completion, error); // Fail any blocked readers/writers, as there will be no writers/readers to pair them with. ChannelUtilities.FailInteractors <ReaderInteractor <T>, T>(_blockedReaders, ChannelUtilities.CreateInvalidCompletionException(error)); ChannelUtilities.FailInteractors <WriterInteractor <T>, VoidResult>(_blockedWriters, ChannelUtilities.CreateInvalidCompletionException(error)); // Let any waiting readers and writers know there won't be any more data ChannelUtilities.WakeUpWaiters(ref _waitingReaders, result: false, error: error); ChannelUtilities.WakeUpWaiters(ref _waitingWriters, result: false, error: error); } return(true); }
private bool TryComplete(Exception error = null) { object blockedReader = null; ReaderInteractor <bool> waitingReader = null; bool completeTask = false; lock (SyncObj) { // If we're already marked as complete, there's nothing more to do. if (_doneWriting != null) { return(false); } // Mark as complete for writing. _doneWriting = error ?? ChannelUtilities.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 (_items.IsEmpty) { completeTask = true; if (_blockedReader != null) { blockedReader = _blockedReader; _blockedReader = null; } if (_waitingReader != null) { waitingReader = _waitingReader; _waitingReader = null; } } } // Complete the channel task if necessary if (completeTask) { ChannelUtilities.Complete(_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); ReaderInteractor <T> interactor = blockedReader as ReaderInteractor <T>; if (interactor != null) { interactor.Fail(error); } else { ((AutoResetAwaiter <T>)blockedReader).SetException(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.Fail(error); } else { waitingReader.Success(false); } } // Successfully completed the channel return(true); }