diff --git a/src/libraries/System.Threading.Channels/src/System.Threading.Channels.csproj b/src/libraries/System.Threading.Channels/src/System.Threading.Channels.csproj
index 4523f5d8b55835..1d8a85a73e0259 100644
--- a/src/libraries/System.Threading.Channels/src/System.Threading.Channels.csproj
+++ b/src/libraries/System.Threading.Channels/src/System.Threading.Channels.csproj
@@ -25,6 +25,7 @@ System.Threading.Channel<T>
+
diff --git a/src/libraries/System.Threading.Channels/src/System/Threading/Channels/BoundedChannel.cs b/src/libraries/System.Threading.Channels/src/System/Threading/Channels/BoundedChannel.cs
index 6508b2d06505c0..ed2d2158bb7bee 100644
--- a/src/libraries/System.Threading.Channels/src/System/Threading/Channels/BoundedChannel.cs
+++ b/src/libraries/System.Threading.Channels/src/System/Threading/Channels/BoundedChannel.cs
@@ -388,13 +388,7 @@ public override bool TryWrite(T item)
// There are no items in the channel, which means we may have blocked/waiting readers.
// Try to get a blocked reader that we can transfer the item to.
- while (ChannelUtilities.TryDequeue(ref parent._blockedReadersHead, out blockedReader))
- {
- if (blockedReader.TryReserveCompletionIfCancelable())
- {
- break;
- }
- }
+ blockedReader = ChannelUtilities.TryDequeueAndReserveCompletionIfCancelable(ref parent._blockedReadersHead);
// If we weren't able to get a reader, instead queue the item and get any waiters that need to be notified.
if (blockedReader is null)
@@ -551,13 +545,7 @@ public override ValueTask WriteAsync(T item, CancellationToken cancellationToken
// There are no items in the channel, which means we may have blocked/waiting readers.
// Try to get a blocked reader that we can transfer the item to.
- while (ChannelUtilities.TryDequeue(ref parent._blockedReadersHead, out blockedReader))
- {
- if (blockedReader.TryReserveCompletionIfCancelable())
- {
- break;
- }
- }
+ blockedReader = ChannelUtilities.TryDequeueAndReserveCompletionIfCancelable(ref parent._blockedReadersHead);
// If we weren't able to get a reader, instead queue the item and get any waiters that need to be notified.
if (blockedReader is null)
diff --git a/src/libraries/System.Threading.Channels/src/System/Threading/Channels/Channel.cs b/src/libraries/System.Threading.Channels/src/System/Threading/Channels/Channel.cs
index 554a60d10724a3..1dbea485b3272b 100644
--- a/src/libraries/System.Threading.Channels/src/System/Threading/Channels/Channel.cs
+++ b/src/libraries/System.Threading.Channels/src/System/Threading/Channels/Channel.cs
@@ -15,6 +15,7 @@ public static Channel CreateUnbounded() =>
/// Specifies the type of data in the channel.
/// Options that guide the behavior of the channel.
/// The created channel.
+ /// is .
public static Channel CreateUnbounded(UnboundedChannelOptions options)
{
ArgumentNullException.ThrowIfNull(options);
@@ -35,35 +36,33 @@ public static Channel CreateUnbounded(UnboundedChannelOptions options)
/// Channels created with this method apply the
/// behavior and prohibit continuations from running synchronously.
///
- public static Channel CreateBounded(int capacity)
- {
- if (capacity < 1)
- {
- throw new ArgumentOutOfRangeException(nameof(capacity));
- }
-
- return new BoundedChannel(capacity, BoundedChannelFullMode.Wait, runContinuationsAsynchronously: true, itemDropped: null);
- }
+ /// is negative.
+ public static Channel CreateBounded(int capacity) =>
+ capacity > 0 ? new BoundedChannel(capacity, BoundedChannelFullMode.Wait, runContinuationsAsynchronously: true, itemDropped: null) :
+ capacity == 0 ? new RendezvousChannel(BoundedChannelFullMode.Wait, runContinuationsAsynchronously: true, itemDropped: null) :
+ throw new ArgumentOutOfRangeException(nameof(capacity));
/// Creates a channel subject to the provided options.
/// Specifies the type of data in the channel.
/// Options that guide the behavior of the channel.
/// The created channel.
- public static Channel CreateBounded(BoundedChannelOptions options)
- {
- return CreateBounded(options, itemDropped: null);
- }
+ /// is .
+ public static Channel CreateBounded(BoundedChannelOptions options) =>
+ CreateBounded(options, itemDropped: null);
/// Creates a channel subject to the provided options.
/// Specifies the type of data in the channel.
/// Options that guide the behavior of the channel.
/// Delegate that will be called when item is being dropped from channel. See .
/// The created channel.
+ /// is .
public static Channel CreateBounded(BoundedChannelOptions options, Action? itemDropped)
{
ArgumentNullException.ThrowIfNull(options);
- return new BoundedChannel(options.Capacity, options.FullMode, !options.AllowSynchronousContinuations, itemDropped);
+ return
+ options.Capacity > 0 ? new BoundedChannel(options.Capacity, options.FullMode, !options.AllowSynchronousContinuations, itemDropped) :
+ new RendezvousChannel(options.FullMode, !options.AllowSynchronousContinuations, itemDropped);
}
}
}
diff --git a/src/libraries/System.Threading.Channels/src/System/Threading/Channels/ChannelOptions.cs b/src/libraries/System.Threading.Channels/src/System/Threading/Channels/ChannelOptions.cs
index ab3a722bb909d2..85a2ff49e29983 100644
--- a/src/libraries/System.Threading.Channels/src/System/Threading/Channels/ChannelOptions.cs
+++ b/src/libraries/System.Threading.Channels/src/System/Threading/Channels/ChannelOptions.cs
@@ -49,9 +49,10 @@ public sealed class BoundedChannelOptions : ChannelOptions
/// Initializes the options.
/// The maximum number of items the bounded channel may store.
+ /// is negative.
public BoundedChannelOptions(int capacity)
{
- if (capacity < 1)
+ if (capacity < 0)
{
throw new ArgumentOutOfRangeException(nameof(capacity));
}
@@ -60,12 +61,13 @@ public BoundedChannelOptions(int capacity)
}
/// Gets or sets the maximum number of items the bounded channel may store.
+ /// is negative.
public int Capacity
{
get => _capacity;
set
{
- if (value < 1)
+ if (value < 0)
{
throw new ArgumentOutOfRangeException(nameof(value));
}
@@ -74,6 +76,7 @@ public int Capacity
}
/// Gets or sets the behavior incurred by write operations when the channel is full.
+ /// is an invalid enum value.
public BoundedChannelFullMode FullMode
{
get => _mode;
diff --git a/src/libraries/System.Threading.Channels/src/System/Threading/Channels/ChannelUtilities.cs b/src/libraries/System.Threading.Channels/src/System/Threading/Channels/ChannelUtilities.cs
index 3436834636edb3..8bfd1889482622 100644
--- a/src/libraries/System.Threading.Channels/src/System/Threading/Channels/ChannelUtilities.cs
+++ b/src/libraries/System.Threading.Channels/src/System/Threading/Channels/ChannelUtilities.cs
@@ -66,6 +66,23 @@ internal static ValueTask GetInvalidCompletionValueTask(Exception error)
return new ValueTask(t);
}
+ /// Dequeues from until an element is dequeued that can have completion reserved.
+ /// The head of the list, with items dequeued up through the returned element, or entirely if is returned.
+ /// The operation on which completion has been reserved, or null if none can be found.
+ internal static TAsyncOp? TryDequeueAndReserveCompletionIfCancelable(ref TAsyncOp? head)
+ where TAsyncOp : AsyncOperation
+ {
+ while (ChannelUtilities.TryDequeue(ref head, out var op))
+ {
+ if (op.TryReserveCompletionIfCancelable())
+ {
+ return op;
+ }
+ }
+
+ return null;
+ }
+
/// Dequeues an operation from the circular doubly-linked list referenced by .
/// The head of the list.
/// The dequeued operation.
@@ -317,6 +334,29 @@ internal static void AssertAll(TAsyncOp? head, Func co
}
}
+ /// Counts the number of operations in the list.
+ /// The head of the queue of operations to count.
+ internal static long CountOperations(TAsyncOp? head)
+ where TAsyncOp : AsyncOperation
+ {
+ TAsyncOp? current = head;
+ long count = 0;
+
+ if (current is not null)
+ {
+ do
+ {
+ count++;
+
+ Debug.Assert(current is not null);
+ current = current.Next;
+ }
+ while (current != head);
+ }
+
+ return count;
+ }
+
/// Creates and returns an exception object to indicate that a channel has been closed.
internal static Exception CreateInvalidCompletionException(Exception? inner = null) =>
inner is OperationCanceledException ? inner :
diff --git a/src/libraries/System.Threading.Channels/src/System/Threading/Channels/RendezvousChannel.cs b/src/libraries/System.Threading.Channels/src/System/Threading/Channels/RendezvousChannel.cs
new file mode 100644
index 00000000000000..7861298fb7263a
--- /dev/null
+++ b/src/libraries/System.Threading.Channels/src/System/Threading/Channels/RendezvousChannel.cs
@@ -0,0 +1,513 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading.Tasks;
+
+namespace System.Threading.Channels
+{
+ /// Provides an unbuffered channel where readers and writers must directly hand off to each other.
+ [DebuggerDisplay("{DebuggerDisplay,nq}")]
+ internal sealed class RendezvousChannel : Channel
+ {
+ /// Whether to suceed writes immediately even when there's no rendezvousing reader.
+ private readonly bool _dropWrites;
+
+ /// The delegate that will be invoked when the channel hits its bound and an item is dropped from the channel.
+ private readonly Action? _itemDropped;
+
+ /// Task signaled when the channel has completed.
+ private readonly TaskCompletionSource _completion;
+
+ /// Head of linked list of blocked ReadAsync calls.
+ private BlockedReadAsyncOperation? _blockedReadersHead;
+
+ /// Head of linked list of blocked WriteAsync calls.
+ private BlockedWriteAsyncOperation? _blockedWritersHead;
+
+ /// Head of linked list of waiting WaitToReadAsync calls.
+ private WaitingReadAsyncOperation? _waitingReadersHead;
+
+ /// Head of linked list of waiting WaitToWriteAsync calls.
+ private WaitingWriteAsyncOperation? _waitingWritersHead;
+
+ /// Whether to force continuations to be executed asynchronously from producer writes.
+ private readonly bool _runContinuationsAsynchronously;
+
+ /// Set to non-null once Complete has been called.
+ private Exception? _doneWriting;
+
+ /// Initializes the .
+ /// The mode used when writing to a full channel.
+ /// Whether to force continuations to be executed asynchronously.
+ /// Delegate that will be invoked when an item is dropped from the channel. See .
+ internal RendezvousChannel(BoundedChannelFullMode mode, bool runContinuationsAsynchronously, Action? itemDropped)
+ {
+ _dropWrites = mode is not BoundedChannelFullMode.Wait;
+
+ _runContinuationsAsynchronously = runContinuationsAsynchronously;
+ _itemDropped = itemDropped;
+ _completion = new TaskCompletionSource(runContinuationsAsynchronously ? TaskCreationOptions.RunContinuationsAsynchronously : TaskCreationOptions.None);
+
+ Reader = new RendezvousChannelReader(this);
+ Writer = new RendezvousChannelWriter(this);
+ }
+
+ [DebuggerDisplay("{DebuggerDisplay,nq}")]
+ private sealed class RendezvousChannelReader : ChannelReader
+ {
+ internal readonly RendezvousChannel _parent;
+ private readonly BlockedReadAsyncOperation _readerSingleton;
+ private readonly WaitingReadAsyncOperation _waiterSingleton;
+
+ internal RendezvousChannelReader(RendezvousChannel parent)
+ {
+ _parent = parent;
+ _readerSingleton = new BlockedReadAsyncOperation(parent._runContinuationsAsynchronously, pooled: true);
+ _waiterSingleton = new WaitingReadAsyncOperation(parent._runContinuationsAsynchronously, pooled: true);
+ }
+
+ public override Task Completion => _parent._completion.Task;
+
+ public override bool CanCount => true;
+
+ public override bool CanPeek => true;
+
+ public override int Count => 0;
+
+ public override bool TryRead([MaybeNullWhen(false)] out T item)
+ {
+ RendezvousChannel parent = _parent;
+
+ // Reserve a blocked writer if one is available.
+ BlockedWriteAsyncOperation? blockedWriter = null;
+ lock (parent.SyncObj)
+ {
+ parent.AssertInvariants();
+
+ if (parent._doneWriting is null)
+ {
+ blockedWriter = ChannelUtilities.TryDequeueAndReserveCompletionIfCancelable(ref parent._blockedWritersHead);
+ }
+ }
+
+ // If we got one, transfer its item to the read and complete successfully.
+ if (blockedWriter is not null)
+ {
+ item = blockedWriter.Item!;
+ blockedWriter.DangerousSetResult(default);
+ return true;
+ }
+
+ item = default;
+ return false;
+ }
+
+ public override bool TryPeek([MaybeNullWhen(false)] out T item)
+ {
+ RendezvousChannel parent = _parent;
+
+ // Peek at a blocked writer if one is available.
+ lock (parent.SyncObj)
+ {
+ parent.AssertInvariants();
+
+ if (parent._doneWriting is null &&
+ parent._blockedWritersHead is { } blockedWriter)
+ {
+ item = blockedWriter.Item!;
+ return true;
+ }
+ }
+
+ item = default;
+ return false;
+ }
+
+ public override ValueTask ReadAsync(CancellationToken cancellationToken)
+ {
+ RendezvousChannel parent = _parent;
+
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return new ValueTask(Task.FromCanceled(cancellationToken));
+ }
+
+ BlockedReadAsyncOperation? reader = null;
+ WaitingWriteAsyncOperation? waitingWriters = null;
+ BlockedWriteAsyncOperation? blockedWriter = null;
+ lock (parent.SyncObj)
+ {
+ parent.AssertInvariants();
+
+ // If we're done writing so that there will never be more items, fail.
+ if (parent._doneWriting is not null)
+ {
+ return ChannelUtilities.GetInvalidCompletionValueTask(parent._doneWriting);
+ }
+
+ // Reserve a blocked writer if one is available.
+ blockedWriter = ChannelUtilities.TryDequeueAndReserveCompletionIfCancelable(ref parent._blockedWritersHead);
+
+ // If we couldn't get one, create a waiting reader, and reserve any waiting writers to alert.
+ if (blockedWriter is null)
+ {
+ reader =
+ !cancellationToken.CanBeCanceled && _readerSingleton.TryOwnAndReset() ? _readerSingleton :
+ new(parent._runContinuationsAsynchronously, cancellationToken, cancellationCallback: _parent.CancellationCallbackDelegate);
+ ChannelUtilities.Enqueue(ref parent._blockedReadersHead, reader);
+
+ waitingWriters = ChannelUtilities.TryReserveCompletionIfCancelable(ref parent._waitingWritersHead);
+ }
+ }
+
+ // Either complete the reserved blocked writer, transferring its item to the read,
+ // or return the waiting reader task, also alerting any waiting writers.
+ ValueTask result;
+ if (blockedWriter is not null)
+ {
+ Debug.Assert(reader is null);
+ Debug.Assert(waitingWriters is null);
+ result = new(blockedWriter.Item!);
+ blockedWriter.DangerousSetResult(default);
+ }
+ else
+ {
+ Debug.Assert(reader is not null);
+ ChannelUtilities.DangerousSetOperations(waitingWriters, result: true);
+ result = reader.ValueTaskOfT;
+ }
+
+ return result;
+ }
+
+ public override ValueTask WaitToReadAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return new ValueTask(Task.FromCanceled(cancellationToken));
+ }
+
+ RendezvousChannel parent = _parent;
+ lock (parent.SyncObj)
+ {
+ parent.AssertInvariants();
+
+ // If we're done writing, a read will never be possible.
+ if (parent._doneWriting is not null)
+ {
+ return parent._doneWriting != ChannelUtilities.s_doneWritingSentinel ?
+ new ValueTask(Task.FromException(parent._doneWriting)) :
+ default;
+ }
+
+ // If there are any writers waiting, a read is possible.
+ if (parent._blockedWritersHead is not null)
+ {
+ return new ValueTask(true);
+ }
+
+ // Register a waiting reader task.
+ WaitingReadAsyncOperation waiter =
+ !cancellationToken.CanBeCanceled && _waiterSingleton.TryOwnAndReset() ? _waiterSingleton :
+ new(parent._runContinuationsAsynchronously, cancellationToken, cancellationCallback: _parent.CancellationCallbackDelegate);
+ ChannelUtilities.Enqueue(ref parent._waitingReadersHead, waiter);
+ return waiter.ValueTaskOfT;
+ }
+ }
+
+ internal string DebuggerDisplay
+ {
+ get
+ {
+ long blockedReaderCount, waitingReaderCount;
+ lock (_parent.SyncObj)
+ {
+ blockedReaderCount = ChannelUtilities.CountOperations(_parent._blockedReadersHead);
+ waitingReaderCount = ChannelUtilities.CountOperations(_parent._waitingReadersHead);
+ }
+
+ return $"ReadAsync={blockedReaderCount}, WaitToReadAsync={waitingReaderCount}";
+ }
+ }
+ }
+
+ [DebuggerDisplay("{DebuggerDisplay,nq}")]
+ private sealed class RendezvousChannelWriter : ChannelWriter
+ {
+ internal readonly RendezvousChannel _parent;
+ private readonly BlockedWriteAsyncOperation _writerSingleton;
+ private readonly WaitingWriteAsyncOperation _waiterSingleton;
+
+ internal RendezvousChannelWriter(RendezvousChannel parent)
+ {
+ _parent = parent;
+ _writerSingleton = new BlockedWriteAsyncOperation(runContinuationsAsynchronously: true, pooled: true);
+ _waiterSingleton = new WaitingWriteAsyncOperation(runContinuationsAsynchronously: true, pooled: true);
+ }
+
+ public override bool TryComplete(Exception? error)
+ {
+ RendezvousChannel parent = _parent;
+
+ BlockedReadAsyncOperation? blockedReadersHead;
+ BlockedWriteAsyncOperation? blockedWritersHead;
+ WaitingReadAsyncOperation? waitingReadersHead;
+ WaitingWriteAsyncOperation? waitingWritersHead;
+ lock (parent.SyncObj)
+ {
+ parent.AssertInvariants();
+
+ // If we've already marked the channel as completed, bail.
+ if (parent._doneWriting is not null)
+ {
+ return false;
+ }
+
+ // Mark that we're done writing.
+ parent._doneWriting = error ?? ChannelUtilities.s_doneWritingSentinel;
+
+ // Snag the queues while holding the lock, so that we don't need to worry
+ // about concurrent mutation, such as from cancellation of pending operations.
+ blockedReadersHead = parent._blockedReadersHead;
+ blockedWritersHead = parent._blockedWritersHead;
+ waitingReadersHead = parent._waitingReadersHead;
+ waitingWritersHead = parent._waitingWritersHead;
+ parent._blockedReadersHead = null;
+ parent._blockedWritersHead = null;
+ parent._waitingReadersHead = null;
+ parent._waitingWritersHead = null;
+ }
+
+ // 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.
+ ChannelUtilities.Complete(parent._completion, error);
+
+ // Complete all pending operations. We don't need to worry about concurrent mutation here:
+ // No other writers or readers will be able to register operations, and any cancellation callbacks
+ // will see the queues as being null and exit immediately.
+ ChannelUtilities.FailOperations(blockedReadersHead, ChannelUtilities.CreateInvalidCompletionException(error));
+ ChannelUtilities.FailOperations(blockedWritersHead, ChannelUtilities.CreateInvalidCompletionException(error));
+ ChannelUtilities.SetOrFailOperations(waitingReadersHead, result: false, error: error);
+ ChannelUtilities.SetOrFailOperations(waitingWritersHead, result: false, error: error);
+
+ // Successfully transitioned to completed.
+ return true;
+ }
+
+ public override bool TryWrite(T item)
+ {
+ RendezvousChannel parent = _parent;
+
+ // Reserve a blocked reader if one is available.
+ BlockedReadAsyncOperation? blockedReader = null;
+ lock (parent.SyncObj)
+ {
+ parent.AssertInvariants();
+
+ if (parent._doneWriting is null)
+ {
+ blockedReader = ChannelUtilities.TryDequeueAndReserveCompletionIfCancelable(ref parent._blockedReadersHead);
+ }
+ }
+
+ // If we got one, transfer its item to the read and complete successfully.
+ if (blockedReader is not null)
+ {
+ blockedReader.DangerousSetResult(item);
+ return true;
+ }
+
+ // There's no concurrent reader, but if we're configured to drop writes, we can succeed immediately.
+ if (parent._dropWrites)
+ {
+ parent._itemDropped?.Invoke(item);
+ return true;
+ }
+
+ return false;
+ }
+
+ public override ValueTask WaitToWriteAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return new ValueTask(Task.FromCanceled(cancellationToken));
+ }
+
+ RendezvousChannel parent = _parent;
+ lock (parent.SyncObj)
+ {
+ parent.AssertInvariants();
+
+ // If we're done writing, a read will never be possible.
+ if (parent._doneWriting is not null)
+ {
+ return parent._doneWriting != ChannelUtilities.s_doneWritingSentinel ?
+ new ValueTask(Task.FromException(parent._doneWriting)) :
+ default;
+ }
+
+ // If there are any readers waiting, a write is possible.
+ if (parent._blockedReadersHead is not null || parent._dropWrites)
+ {
+ return new ValueTask(true);
+ }
+
+ // There were no readers available, but there could be in the future, so ensure
+ // there's a waiting writer task and return it.
+ WaitingWriteAsyncOperation waiter =
+ !cancellationToken.CanBeCanceled && _waiterSingleton.TryOwnAndReset() ? _waiterSingleton :
+ new(parent._runContinuationsAsynchronously, cancellationToken, cancellationCallback: _parent.CancellationCallbackDelegate);
+ ChannelUtilities.Enqueue(ref parent._waitingWritersHead, waiter);
+ return waiter.ValueTaskOfT;
+ }
+ }
+
+ public override ValueTask WriteAsync(T item, CancellationToken cancellationToken)
+ {
+ RendezvousChannel parent = _parent;
+
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return new ValueTask(Task.FromCanceled(cancellationToken));
+ }
+
+ BlockedWriteAsyncOperation? writer = null;
+ WaitingReadAsyncOperation? waitingReaders = null;
+ BlockedReadAsyncOperation? blockedReader = null;
+ lock (parent.SyncObj)
+ {
+ parent.AssertInvariants();
+
+ // If we've already been marked as done for writing, we shouldn't be writing.
+ if (parent._doneWriting is not null)
+ {
+ return new ValueTask(Task.FromException(ChannelUtilities.CreateInvalidCompletionException(parent._doneWriting)));
+ }
+
+ // Reserve a blocked reader if one is available.
+ blockedReader = ChannelUtilities.TryDequeueAndReserveCompletionIfCancelable(ref parent._blockedReadersHead);
+
+ // If we couldn't get one, create a blocked writer, and reserve any waiting readers to alert.
+ if (blockedReader is null && !parent._dropWrites)
+ {
+ writer =
+ !cancellationToken.CanBeCanceled && _writerSingleton.TryOwnAndReset() ? _writerSingleton :
+ new(parent._runContinuationsAsynchronously, cancellationToken, cancellationCallback: _parent.CancellationCallbackDelegate);
+ writer.Item = item;
+ ChannelUtilities.Enqueue(ref parent._blockedWritersHead, writer);
+
+ waitingReaders = ChannelUtilities.TryReserveCompletionIfCancelable(ref parent._waitingReadersHead);
+ }
+ }
+
+ if (writer is not null)
+ {
+ Debug.Assert(blockedReader is null);
+ ChannelUtilities.DangerousSetOperations(waitingReaders, result: true);
+ return writer.ValueTask;
+ }
+
+ if (blockedReader is not null)
+ {
+ blockedReader.DangerousSetResult(item);
+ }
+ else
+ {
+ Debug.Assert(parent._dropWrites);
+ parent._itemDropped?.Invoke(item);
+ }
+
+ return default;
+ }
+
+ internal string DebuggerDisplay
+ {
+ get
+ {
+ long blockedWriterCount, waitingWriterCount;
+ lock (_parent.SyncObj)
+ {
+ blockedWriterCount = ChannelUtilities.CountOperations(_parent._blockedWritersHead);
+ waitingWriterCount = ChannelUtilities.CountOperations(_parent._waitingWritersHead);
+ }
+
+ return $"WriteAsync={blockedWriterCount}, WaitToWriteAsync={waitingWriterCount}";
+ }
+ }
+ }
+
+ /// Gets an object used to synchronize all state on the instance.
+ private object SyncObj => _completion;
+
+ private Action