55using System . Collections . Generic ;
66using System . Diagnostics ;
77using System . Diagnostics . CodeAnalysis ;
8+ using System . Runtime . CompilerServices ;
89using System . Threading . Tasks ;
910
1011namespace System . Threading . Channels
1112{
1213 /// <summary>Provides a buffered channel of unbounded capacity.</summary>
1314 [ DebuggerDisplay ( "Items = {ItemsCountForDebugger}, Closed = {ChannelIsClosedForDebugger}" ) ]
14- [ DebuggerTypeProxy ( typeof ( DebugEnumeratorDebugView < > ) ) ]
15- internal sealed class UnboundedChannel < T > : Channel < T > , IDebugEnumerable < T >
15+ [ DebuggerTypeProxy ( typeof ( DebugEnumeratorDebugView < , > ) ) ]
16+ internal sealed class UnboundedChannel < T , TQueue > : Channel < T > , IDebugEnumerable < T > where TQueue : struct , IUnboundedChannelQueue < T >
1617 {
1718 /// <summary>Task that indicates the channel has completed.</summary>
1819 private readonly TaskCompletionSource _completion ;
1920 /// <summary>The items in the channel.</summary>
20- private readonly ConcurrentQueue < T > _items = new ConcurrentQueue < T > ( ) ;
21+ private readonly TQueue _items ;
2122 /// <summary>Readers blocked reading from the channel.</summary>
2223 private readonly Deque < AsyncOperation < T > > _blockedReaders = new Deque < AsyncOperation < T > > ( ) ;
2324 /// <summary>Whether to force continuations to be executed asynchronously from producer writes.</summary>
@@ -29,23 +30,24 @@ internal sealed class UnboundedChannel<T> : Channel<T>, IDebugEnumerable<T>
2930 private Exception ? _doneWriting ;
3031
3132 /// <summary>Initialize the channel.</summary>
32- internal UnboundedChannel ( bool runContinuationsAsynchronously )
33+ internal UnboundedChannel ( TQueue items , bool runContinuationsAsynchronously )
3334 {
35+ _items = items ;
3436 _runContinuationsAsynchronously = runContinuationsAsynchronously ;
3537 _completion = new TaskCompletionSource ( runContinuationsAsynchronously ? TaskCreationOptions . RunContinuationsAsynchronously : TaskCreationOptions . None ) ;
3638 Reader = new UnboundedChannelReader ( this ) ;
3739 Writer = new UnboundedChannelWriter ( this ) ;
3840 }
3941
4042 [ DebuggerDisplay ( "Items = {Count}" ) ]
41- [ DebuggerTypeProxy ( typeof ( DebugEnumeratorDebugView < > ) ) ]
43+ [ DebuggerTypeProxy ( typeof ( DebugEnumeratorDebugView < , > ) ) ]
4244 private sealed class UnboundedChannelReader : ChannelReader < T > , IDebugEnumerable < T >
4345 {
44- internal readonly UnboundedChannel < T > _parent ;
46+ internal readonly UnboundedChannel < T , TQueue > _parent ;
4547 private readonly AsyncOperation < T > _readerSingleton ;
4648 private readonly AsyncOperation < bool > _waiterSingleton ;
4749
48- internal UnboundedChannelReader ( UnboundedChannel < T > parent )
50+ internal UnboundedChannelReader ( UnboundedChannel < T , TQueue > parent )
4951 {
5052 _parent = parent ;
5153 _readerSingleton = new AsyncOperation < T > ( parent . _runContinuationsAsynchronously , pooled : true ) ;
@@ -68,8 +70,8 @@ public override ValueTask<T> ReadAsync(CancellationToken cancellationToken)
6870 }
6971
7072 // Dequeue an item if we can.
71- UnboundedChannel < T > parent = _parent ;
72- if ( parent . _items . TryDequeue ( out T ? item ) )
73+ UnboundedChannel < T , TQueue > parent = _parent ;
74+ if ( parent . _items . IsThreadSafe && parent . _items . TryDequeue ( out T ? item ) )
7375 {
7476 CompleteIfDone ( parent ) ;
7577 return new ValueTask < T > ( item ) ;
@@ -112,24 +114,60 @@ public override ValueTask<T> ReadAsync(CancellationToken cancellationToken)
112114
113115 public override bool TryRead ( [ MaybeNullWhen ( false ) ] out T item )
114116 {
115- UnboundedChannel < T > parent = _parent ;
117+ UnboundedChannel < T , TQueue > parent = _parent ;
118+ return parent . _items . IsThreadSafe ?
119+ LockFree ( parent , out item ) :
120+ Locked ( parent , out item ) ;
116121
117- // Dequeue an item if we can
118- if ( parent . _items . TryDequeue ( out item ) )
122+ static bool LockFree ( UnboundedChannel < T , TQueue > parent , [ MaybeNullWhen ( false ) ] out T item )
119123 {
120- CompleteIfDone ( parent ) ;
121- return true ;
124+ if ( parent . _items . TryDequeue ( out item ) )
125+ {
126+ CompleteIfDone ( parent ) ;
127+ return true ;
128+ }
129+
130+ item = default ;
131+ return false ;
122132 }
123133
124- item = default ;
125- return false ;
134+ static bool Locked ( UnboundedChannel < T , TQueue > parent , [ MaybeNullWhen ( false ) ] out T item )
135+ {
136+ lock ( parent . SyncObj )
137+ {
138+ if ( parent . _items . TryDequeue ( out item ) )
139+ {
140+ CompleteIfDone ( parent ) ;
141+ return true ;
142+ }
143+ }
144+
145+ item = default ;
146+ return false ;
147+ }
126148 }
127149
128- public override bool TryPeek ( [ MaybeNullWhen ( false ) ] out T item ) =>
129- _parent . _items . TryPeek ( out item ) ;
150+ public override bool TryPeek ( [ MaybeNullWhen ( false ) ] out T item )
151+ {
152+ UnboundedChannel < T , TQueue > parent = _parent ;
153+ return parent . _items . IsThreadSafe ?
154+ parent . _items . TryPeek ( out item ) :
155+ Locked ( parent , out item ) ;
156+
157+ // Separated out to keep the try/finally from preventing TryPeek from being inlined
158+ static bool Locked ( UnboundedChannel < T , TQueue > parent , [ MaybeNullWhen ( false ) ] out T item )
159+ {
160+ lock ( parent . SyncObj )
161+ {
162+ return parent . _items . TryPeek ( out item ) ;
163+ }
164+ }
165+ }
130166
131- private static void CompleteIfDone ( UnboundedChannel < T > parent )
167+ private static void CompleteIfDone ( UnboundedChannel < T , TQueue > parent )
132168 {
169+ Debug . Assert ( parent . _items . IsThreadSafe || Monitor . IsEntered ( parent . SyncObj ) ) ;
170+
133171 if ( parent . _doneWriting != null && parent . _items . IsEmpty )
134172 {
135173 // If we've now emptied the items queue and we're not getting any more, complete.
@@ -144,12 +182,12 @@ public override ValueTask<bool> WaitToReadAsync(CancellationToken cancellationTo
144182 return new ValueTask < bool > ( Task . FromCanceled < bool > ( cancellationToken ) ) ;
145183 }
146184
147- if ( ! _parent . _items . IsEmpty )
185+ if ( _parent . _items . IsThreadSafe && ! _parent . _items . IsEmpty )
148186 {
149187 return new ValueTask < bool > ( true ) ;
150188 }
151189
152- UnboundedChannel < T > parent = _parent ;
190+ UnboundedChannel < T , TQueue > parent = _parent ;
153191
154192 lock ( parent . SyncObj )
155193 {
@@ -192,15 +230,15 @@ public override ValueTask<bool> WaitToReadAsync(CancellationToken cancellationTo
192230 }
193231
194232 [ DebuggerDisplay ( "Items = {ItemsCountForDebugger}" ) ]
195- [ DebuggerTypeProxy ( typeof ( DebugEnumeratorDebugView < > ) ) ]
233+ [ DebuggerTypeProxy ( typeof ( DebugEnumeratorDebugView < , > ) ) ]
196234 private sealed class UnboundedChannelWriter : ChannelWriter < T > , IDebugEnumerable < T >
197235 {
198- internal readonly UnboundedChannel < T > _parent ;
199- internal UnboundedChannelWriter ( UnboundedChannel < T > parent ) => _parent = parent ;
236+ internal readonly UnboundedChannel < T , TQueue > _parent ;
237+ internal UnboundedChannelWriter ( UnboundedChannel < T , TQueue > parent ) => _parent = parent ;
200238
201239 public override bool TryComplete ( Exception ? error )
202240 {
203- UnboundedChannel < T > parent = _parent ;
241+ UnboundedChannel < T , TQueue > parent = _parent ;
204242 bool completeTask ;
205243
206244 lock ( parent . SyncObj )
@@ -240,7 +278,7 @@ public override bool TryComplete(Exception? error)
240278
241279 public override bool TryWrite ( T item )
242280 {
243- UnboundedChannel < T > parent = _parent ;
281+ UnboundedChannel < T , TQueue > parent = _parent ;
244282 while ( true )
245283 {
246284 AsyncOperation < T > ? blockedReader = null ;
@@ -321,7 +359,7 @@ public override ValueTask WriteAsync(T item, CancellationToken cancellationToken
321359 }
322360
323361 /// <summary>Gets the object used to synchronize access to all state on this instance.</summary>
324- private object SyncObj => _items ;
362+ private object SyncObj => _blockedReaders ;
325363
326364 [ Conditional ( "DEBUG" ) ]
327365 private void AssertInvariants ( )
0 commit comments