@@ -41,8 +41,16 @@ protected QueuedSynchronizer()
4141 /// </summary>
4242 /// <param name="context">The context associated with the suspended caller or supplied externally.</param>
4343 /// <returns><see langword="true"/> if acquisition is allowed; otherwise, <see langword="false"/>.</returns>
44+ /// <exception cref="Exception">A custom exception is thrown.</exception>
4445 protected abstract bool CanAcquire ( TContext context ) ;
4546
47+ /// <summary>
48+ /// Returns an optional exception if <see cref="CanAcquire"/> returns <see langword="false"/>.
49+ /// </summary>
50+ /// <param name="context">The acquisition context.</param>
51+ /// <returns>The exception; or <see langword="null"/>.</returns>
52+ protected virtual Exception ? GetAcquisitionException ( TContext context ) => null ;
53+
4654 /// <summary>
4755 /// Modifies the internal state according to acquisition semantics.
4856 /// </summary>
@@ -69,11 +77,19 @@ private protected sealed override void DrainWaitQueue(ref WaitQueueVisitor waitQ
6977 {
7078 for ( ; ! waitQueueVisitor . IsEndOfQueue < WaitNode , TContext > ( out var context ) ; waitQueueVisitor . Advance ( ) )
7179 {
72- if ( ! CanAcquire ( context ) )
73- break ;
74-
75- if ( waitQueueVisitor . SignalCurrent ( ) )
76- AcquireCore ( context ) ;
80+ switch ( CanAcquire ( context ) )
81+ {
82+ case false when GetAcquisitionException ( context ) is { } e :
83+ waitQueueVisitor . SignalCurrent ( e ) ;
84+ goto default ;
85+ case false :
86+ return ;
87+ case true when waitQueueVisitor . SignalCurrent ( ) :
88+ AcquireCore ( context ) ;
89+ goto default ;
90+ default :
91+ continue ;
92+ }
7793 }
7894 }
7995
@@ -194,6 +210,34 @@ protected ValueTask AcquireAsync(TContext context, CancellationToken token)
194210 return AcquireAsync < ValueTask , CancellationTokenOnly > ( context , ref builder ) ;
195211 }
196212
213+ private T AcquireAsync < T , TBuilder > ( TContext context , ref TBuilder builder )
214+ where T : struct , IEquatable < T >
215+ where TBuilder : struct , ITaskBuilder < T >
216+ {
217+ T result ;
218+ if ( ! builder . IsCompleted )
219+ {
220+ var acquired = TryAcquireCore ( context ) ;
221+ if ( ! acquired && GetAcquisitionException ( context ) is { } e )
222+ {
223+ result = TBuilder . FromException ( e ) ;
224+ goto exit ;
225+ }
226+
227+ if ( Acquire < T , TBuilder , WaitNode > ( ref builder , acquired ) is { } node )
228+ {
229+ node . DrainOnReturn = true ;
230+ node . Context = context ;
231+ }
232+ }
233+
234+ result = builder . Invoke ( ) ;
235+
236+ exit :
237+ builder . Dispose ( ) ;
238+ return result ;
239+ }
240+
197241 private bool TryAcquireCore ( TContext context )
198242 {
199243 Debug . Assert ( Monitor . IsEntered ( SyncRoot ) ) ;
@@ -206,24 +250,4 @@ private bool TryAcquireCore(TContext context)
206250
207251 return false ;
208252 }
209-
210- private T AcquireAsync < T , TBuilder > ( TContext context , ref TBuilder builder )
211- where T : struct , IEquatable < T >
212- where TBuilder : struct , ITaskBuilder < T >
213- {
214- switch ( builder . IsCompleted )
215- {
216- case true :
217- goto default ;
218- case false when Acquire < T , TBuilder , WaitNode > ( ref builder , TryAcquireCore ( context ) ) is { } node :
219- node . DrainOnReturn = true ;
220- node . Context = context ;
221- goto default ;
222- default :
223- builder . Dispose ( ) ;
224- break ;
225- }
226-
227- return builder . Invoke ( ) ;
228- }
229253}
0 commit comments