@@ -29,6 +29,28 @@ public enum SemaphoreWaitResult
2929 Entered
3030 }
3131
32+ private readonly struct CancellationContext : IDisposable
33+ {
34+ public bool IsSignaled { get ; }
35+ public CancellationTokenSource LinkedCancellationTokenSource { get ; }
36+ public CancellationTokenSource SignalCancellationTokenSource { get ; }
37+
38+ public static CancellationContext Signaled { get ; } = new CancellationContext ( true , default , default ) ;
39+
40+ public CancellationContext (
41+ bool isSignaled ,
42+ CancellationTokenSource linkedCancellationTokenSource ,
43+ CancellationTokenSource signaledCancellationTokenSource )
44+ {
45+ IsSignaled = isSignaled ;
46+ LinkedCancellationTokenSource = linkedCancellationTokenSource ;
47+ SignalCancellationTokenSource = signaledCancellationTokenSource ;
48+ }
49+
50+ public CancellationToken CancellationToken => LinkedCancellationTokenSource . Token ;
51+ public void Dispose ( ) => LinkedCancellationTokenSource ? . Dispose ( ) ;
52+ }
53+
3254 public sealed class SemaphoreSlimSignalableAwaiter : IDisposable
3355 {
3456 private readonly SemaphoreSlimSignalable _semaphoreSlimSignalable ;
@@ -91,6 +113,7 @@ public void Reset()
91113 {
92114 if ( _signalCancellationTokenSource . IsCancellationRequested )
93115 {
116+ _signalCancellationTokenSource . Dispose ( ) ;
94117 _signalCancellationTokenSource = new CancellationTokenSource ( ) ;
95118 }
96119 }
@@ -111,21 +134,21 @@ public async Task<SemaphoreWaitResult> WaitAsync(TimeSpan timeout, CancellationT
111134
112135 public SemaphoreWaitResult WaitSignaled ( TimeSpan timeout , CancellationToken cancellationToken )
113136 {
114- var ( tokenSourceLinked , signalTokenSource , signaled ) = GetLinkedTokenAndCheckForSignaled ( cancellationToken ) ;
137+ using var cancellationContext = GetCancellationTokenContext ( cancellationToken ) ;
115138
116- if ( signaled )
139+ if ( cancellationContext . IsSignaled )
117140 {
118141 return SemaphoreWaitResult . Signaled ;
119142 }
120143
121144 try
122145 {
123- var entered = _semaphore . Wait ( timeout , tokenSourceLinked . Token ) ;
146+ var entered = _semaphore . Wait ( timeout , cancellationContext . CancellationToken ) ;
124147 return entered ? SemaphoreWaitResult . Entered : SemaphoreWaitResult . TimedOut ;
125148 }
126149 catch ( OperationCanceledException )
127150 {
128- if ( IsSignaled ( signalTokenSource . Token , cancellationToken ) )
151+ if ( IsSignaled ( cancellationContext . SignalCancellationTokenSource , cancellationToken ) )
129152 {
130153 return SemaphoreWaitResult . Signaled ;
131154 }
@@ -136,22 +159,22 @@ public SemaphoreWaitResult WaitSignaled(TimeSpan timeout, CancellationToken canc
136159
137160 public async Task < SemaphoreWaitResult > WaitSignaledAsync ( TimeSpan timeout , CancellationToken cancellationToken )
138161 {
139- var ( tokenSourceLinked , signalTokenSource , signaled ) = GetLinkedTokenAndCheckForSignaled ( cancellationToken ) ;
162+ using var cancellationContext = GetCancellationTokenContext ( cancellationToken ) ;
140163
141- if ( signaled )
164+ if ( cancellationContext . IsSignaled )
142165 {
143166 return SemaphoreWaitResult . Signaled ;
144167 }
145168
146169 try
147170 {
148- var entered = await _semaphore . WaitAsync ( timeout , tokenSourceLinked . Token ) . ConfigureAwait ( false ) ;
171+ var entered = await _semaphore . WaitAsync ( timeout , cancellationContext . CancellationToken ) . ConfigureAwait ( false ) ;
149172
150173 return entered ? SemaphoreWaitResult . Entered : SemaphoreWaitResult . TimedOut ;
151174 }
152175 catch ( OperationCanceledException )
153176 {
154- if ( IsSignaled ( signalTokenSource . Token , cancellationToken ) )
177+ if ( IsSignaled ( cancellationContext . SignalCancellationTokenSource , cancellationToken ) )
155178 {
156179 // Request task rescheduling, to avoid resuming execution on Signal thread
157180 await TaskExtensions . YieldNoContext ( ) ;
@@ -174,31 +197,46 @@ public void Release()
174197 public void Dispose ( )
175198 {
176199 _semaphore . Dispose ( ) ;
200+ _signalCancellationTokenSource . Dispose ( ) ;
177201 }
178202
179- private ( CancellationTokenSource TokenSourceLinked , CancellationTokenSource SignalTokenSource , bool Signaled ) GetLinkedTokenAndCheckForSignaled ( CancellationToken cancellationToken )
203+ private CancellationContext GetCancellationTokenContext ( CancellationToken cancellationToken )
180204 {
181205 var signalTokenSource = _signalCancellationTokenSource ;
182206
183- if ( IsSignaled ( signalTokenSource . Token , cancellationToken ) )
207+ if ( IsSignaled ( signalTokenSource , cancellationToken ) )
184208 {
185- return ( default , default , true ) ;
209+ return CancellationContext . Signaled ;
186210 }
187211
188- var tokenSourceLinked = CancellationTokenSource . CreateLinkedTokenSource (
189- signalTokenSource . Token ,
190- cancellationToken ) ;
212+ try
213+ {
214+ var cancellationLinkedTokenSource = CancellationTokenSource . CreateLinkedTokenSource (
215+ signalTokenSource . Token ,
216+ cancellationToken ) ;
191217
192- return ( tokenSourceLinked , signalTokenSource , false ) ;
218+ return new CancellationContext ( false , cancellationLinkedTokenSource , signalTokenSource ) ;
219+ }
220+ catch ( ObjectDisposedException )
221+ {
222+ // signalTokenSource was disposed, it will happen only when cancellation was requested for signalTokenSource or on Dispose
223+ return CancellationContext . Signaled ;
224+ }
193225 }
194226
195- #pragma warning disable CA1068 // CancellationToken parameters must come last
196- private bool IsSignaled ( CancellationToken signalToken , CancellationToken cancellationToken )
197- #pragma warning restore CA1068 // CancellationToken parameters must come last
227+ private bool IsSignaled ( CancellationTokenSource signalTokenSource , CancellationToken cancellationToken )
198228 {
199229 cancellationToken . ThrowIfCancellationRequested ( ) ;
200230
201- return signalToken . IsCancellationRequested ;
231+ try
232+ {
233+ return signalTokenSource . Token . IsCancellationRequested ;
234+ }
235+ catch ( ObjectDisposedException )
236+ {
237+ // signalTokenSource was disposed, it will happen only when cancellation was requested for signalTokenSource or on Dispose
238+ return true ;
239+ }
202240 }
203241 }
204242}
0 commit comments