private ValueTask <T> ReadAsync(CancellationToken cancellationToken = default(CancellationToken)) { // Fast-path cancellation check if (cancellationToken.IsCancellationRequested) { return(new ValueTask <T>(Task.FromCanceled <T>(cancellationToken))); } lock (SyncObj) { AssertInvariants(); // If there are any items, hand one back. if (!_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 (_doneWriting != null) { return(ChannelUtilities.GetErrorValueTask <T>(_doneWriting)); } // Otherwise, queue the reader. var reader = ReaderInteractor <T> .Create(_runContinuationsAsynchronously, cancellationToken); _blockedReaders.EnqueueTail(reader); return(new ValueTask <T>(reader.Task)); } }
private ValueTask <T> ReadAsyncCore(CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return(new ValueTask <T>(Task.FromCanceled <T>(cancellationToken))); } lock (SyncObj) { // Now that we hold the lock, try reading again. T item; if (TryRead(out item)) { return(new ValueTask <T>(item)); } // If no more items will be written, fail the read. if (_doneWriting != null) { return(ChannelUtilities.GetInvalidCompletionValueTask <T>(_doneWriting)); } Debug.Assert(_blockedReader == null || ((_blockedReader as ReaderInteractor <T>)?.Task.IsCanceled ?? false), "Incorrect usage; multiple outstanding reads were issued against this single-consumer channel"); // Store the reader to be completed by a writer. ReaderInteractor <T> reader = ReaderInteractor <T> .Create(_runContinuationsAsynchronously, cancellationToken); _blockedReader = reader; return(new ValueTask <T>(reader.Task)); } }
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)); } }
/// <summary>Gets or creates a "waiter" (e.g. WaitForRead/WriteAsync) interactor.</summary> /// <param name="waiter">The field storing the waiter interactor.</param> /// <param name="runContinuationsAsynchronously">true to force continuations to run asynchronously; otherwise, false.</param> /// <param name="cancellationToken">The token to use to cancel the wait.</param> internal static Task <bool> GetOrCreateWaiter(ref ReaderInteractor <bool> waiter, bool runContinuationsAsynchronously, CancellationToken cancellationToken) { // Get the existing waiters interactor. ReaderInteractor <bool> w = waiter; // If there isn't one, create one. This explicitly does not include the cancellation token, // as we reuse it for any number of waiters that overlap. if (w == null) { waiter = w = ReaderInteractor <bool> .Create(runContinuationsAsynchronously); } // If the cancellation token can't be canceled, then just return the waiter task. // If it can, we need to return a task that will complete when the waiter task does but that can also be canceled. // Easiest way to do that is with a cancelable continuation. return(cancellationToken.CanBeCanceled ? w.Task.ContinueWith(t => t.Result, cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default) : w.Task); }
private Task <bool> WaitToReadAsync(CancellationToken cancellationToken = default) { // Outside of the lock, check if there are any items waiting to be read. If there are, we're done. if (!_items.IsEmpty) { return(ChannelUtilities.TrueTask); } // Now check for cancellation. if (cancellationToken.IsCancellationRequested) { return(Task.FromCanceled <bool>(cancellationToken)); } ReaderInteractor <bool> oldWaiter = null, newWaiter; lock (SyncObj) { // Again while holding the lock, check to see if there are any items available. if (!_items.IsEmpty) { return(ChannelUtilities.TrueTask); } // There aren't any items; if we're done writing, there never will be more items. if (_doneWriting != null) { return(_doneWriting != ChannelUtilities.DoneWritingSentinel ? Task.FromException <bool>(_doneWriting) : ChannelUtilities.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 = _waitingReader; _waitingReader = newWaiter = ReaderInteractor <bool> .Create(_runContinuationsAsynchronously, cancellationToken); } oldWaiter?.TrySetCanceled(); return(newWaiter.Task); }
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)); } }