/// <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 ValueTask <T> ReadAsyncCore(CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return(new ValueTask <T>(Task.FromCanceled <T>(cancellationToken))); } lock (SyncObj) { AssertInvariants(); // If we're already completed, nothing to read. if (_completion.Task.IsCompleted) { return(new ValueTask <T>( _completion.Task.IsCanceled ? Task.FromCanceled <T>(new CancellationToken(true)) : Task.FromException <T>( _completion.Task.IsFaulted ? ChannelUtilities.CreateInvalidCompletionException(_completion.Task.Exception.InnerException) : ChannelUtilities.CreateInvalidCompletionException()))); } // If there are any blocked writers, find one to pair up with // and get its data. Writers that got canceled will remain in the queue, // so we need to loop to skip past them. while (!_blockedWriters.IsEmpty) { WriterInteractor <T> w = _blockedWriters.DequeueHead(); if (w.Success(default(VoidResult))) { return(new ValueTask <T>(w.Item)); } } // No writer found to pair with. Queue the reader. var r = ReaderInteractor <T> .Create(true, cancellationToken); _blockedReaders.EnqueueTail(r); // And let any waiting writers know it's their lucky day. ChannelUtilities.WakeUpWaiters(ref _waitingWriters, result: true); return(new ValueTask <T>(r.Task)); } }
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); }
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 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); }