Initial commit
[mono.git] / mcs / class / referencesource / mscorlib / system / threading / SemaphoreSlim.cs
1 // ==++==
2 //
3 //   Copyright (c) Microsoft Corporation.  All rights reserved.
4 // 
5 // ==--==
6 // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
7 //
8 // SemaphoreSlim.cs
9 //
10 // <OWNER>[....]</OWNER>
11 //
12 // A lightweight semahore class that contains the basic semaphore functions plus some useful functions like interrupt
13 // and wait handle exposing to allow waiting on multiple semaphores.
14 //
15 // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
16
17
18 using System;
19 using System.Collections.Generic;
20 using System.Diagnostics;
21 using System.Security;
22 using System.Security.Permissions;
23 using System.Runtime.InteropServices;
24 using System.Diagnostics.Contracts;
25 using System.Threading.Tasks;
26
27 // The class will be part of the current System.Threading namespace
28 namespace System.Threading
29 {
30     /// <summary>
31     /// Limits the number of threads that can access a resource or pool of resources concurrently.
32     /// </summary>
33     /// <remarks>
34     /// <para>
35     /// The <see cref="SemaphoreSlim"/> provides a lightweight semaphore class that doesn't
36     /// use Windows kernel semaphores.
37     /// </para>
38     /// <para>
39     /// All public and protected members of <see cref="SemaphoreSlim"/> are thread-safe and may be used
40     /// concurrently from multiple threads, with the exception of Dispose, which
41     /// must only be used when all other operations on the <see cref="SemaphoreSlim"/> have
42     /// completed.
43     /// </para>
44     /// </remarks>
45     [ComVisible(false)]
46     [HostProtection(Synchronization = true, ExternalThreading = true)]
47     [DebuggerDisplay("Current Count = {m_currentCount}")]
48     public class SemaphoreSlim : IDisposable
49     {
50         #region Private Fields
51
52         // The semaphore count, initialized in the constructor to the initial value, every release call incremetns it
53         // and every wait call decrements it as long as its value is positive otherwise the wait will block.
54         // Its value must be between the maximum semaphore value and zero
55         private volatile int m_currentCount;
56
57         // The maximum semaphore value, it is initialized to Int.MaxValue if the client didn't specify it. it is used 
58         // to check if the count excceeded the maxi value or not.
59         private readonly int m_maxCount;
60
61         // The number of synchronously waiting threads, it is set to zero in the constructor and increments before blocking the
62         // threading and decrements it back after that. It is used as flag for the release call to know if there are
63         // waiting threads in the monitor or not.
64         private volatile int m_waitCount;
65
66         // Dummy object used to in lock statements to protect the semaphore count, wait handle and cancelation
67         private object m_lockObj;
68
69         // Act as the semaphore wait handle, it's lazily initialized if needed, the first WaitHandle call initialize it
70         // and wait an release sets and resets it respectively as long as it is not null
71         private volatile ManualResetEvent m_waitHandle;
72
73         // Head of list representing asynchronous waits on the semaphore.
74         private TaskNode m_asyncHead;
75
76         // Tail of list representing asynchronous waits on the semaphore.
77         private TaskNode m_asyncTail;
78
79         // A pre-completed task with Result==true
80         private readonly static Task<bool> s_trueTask =
81             new Task<bool>(false, true, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default(CancellationToken));
82
83         // No maximum constant
84         private const int NO_MAXIMUM = Int32.MaxValue;
85
86         // Task in a linked list of asynchronous waiters
87         private sealed class TaskNode : Task<bool>, IThreadPoolWorkItem
88         {
89             internal TaskNode Prev, Next;
90             internal TaskNode() : base() {}
91
92             [SecurityCritical]
93             void IThreadPoolWorkItem.ExecuteWorkItem()
94             {
95                 bool setSuccessfully = TrySetResult(true);
96                 Contract.Assert(setSuccessfully, "Should have been able to complete task");
97             }
98
99             [SecurityCritical]
100             void IThreadPoolWorkItem.MarkAborted(ThreadAbortException tae) { /* nop */ }
101         }
102         #endregion
103
104         #region Public properties
105
106         /// <summary>
107         /// Gets the current count of the <see cref="SemaphoreSlim"/>.
108         /// </summary>
109         /// <value>The current count of the <see cref="SemaphoreSlim"/>.</value>
110         public int CurrentCount
111         {
112             get { return m_currentCount; }
113         }
114
115         /// <summary>
116         /// Returns a <see cref="T:System.Threading.WaitHandle"/> that can be used to wait on the semaphore.
117         /// </summary>
118         /// <value>A <see cref="T:System.Threading.WaitHandle"/> that can be used to wait on the
119         /// semaphore.</value>
120         /// <remarks>
121         /// A successful wait on the <see cref="AvailableWaitHandle"/> does not imply a successful wait on
122         /// the <see cref="SemaphoreSlim"/> itself, nor does it decrement the semaphore's
123         /// count. <see cref="AvailableWaitHandle"/> exists to allow a thread to block waiting on multiple
124         /// semaphores, but such a wait should be followed by a true wait on the target semaphore.
125         /// </remarks>
126         /// <exception cref="T:System.ObjectDisposedException">The <see
127         /// cref="SemaphoreSlim"/> has been disposed.</exception>
128         public WaitHandle AvailableWaitHandle
129         {
130             get
131             {
132                 CheckDispose();
133
134                 // Return it directly if it is not null
135                 if (m_waitHandle != null)
136                     return m_waitHandle;
137
138                 //lock the count to avoid multiple threads initializing the handle if it is null
139                 lock (m_lockObj)
140                 {
141                     if (m_waitHandle == null)
142                     {
143                         // The initial state for the wait handle is true if the count is greater than zero
144                         // false otherwise
145                         m_waitHandle = new ManualResetEvent(m_currentCount != 0);
146                     }
147                 }
148                 return m_waitHandle;
149             }
150         }
151
152         #endregion
153
154         #region Constructors
155         /// <summary>
156         /// Initializes a new instance of the <see cref="SemaphoreSlim"/> class, specifying
157         /// the initial number of requests that can be granted concurrently.
158         /// </summary>
159         /// <param name="initialCount">The initial number of requests for the semaphore that can be granted
160         /// concurrently.</param>
161         /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="initialCount"/>
162         /// is less than 0.</exception>
163         public SemaphoreSlim(int initialCount)
164             : this(initialCount, NO_MAXIMUM)
165         {
166         }
167
168         /// <summary>
169         /// Initializes a new instance of the <see cref="SemaphoreSlim"/> class, specifying
170         /// the initial and maximum number of requests that can be granted concurrently.
171         /// </summary>
172         /// <param name="initialCount">The initial number of requests for the semaphore that can be granted
173         /// concurrently.</param>
174         /// <param name="maxCount">The maximum number of requests for the semaphore that can be granted
175         /// concurrently.</param>
176         /// <exception cref="T:System.ArgumentOutOfRangeException"> <paramref name="initialCount"/>
177         /// is less than 0. -or-
178         /// <paramref name="initialCount"/> is greater than <paramref name="maxCount"/>. -or-
179         /// <paramref name="maxCount"/> is less than 0.</exception>
180         public SemaphoreSlim(int initialCount, int maxCount)
181         {
182             if (initialCount < 0 || initialCount > maxCount)
183             {
184                 throw new ArgumentOutOfRangeException(
185                     "initialCount", initialCount, GetResourceString("SemaphoreSlim_ctor_InitialCountWrong"));
186             }
187
188             //validate input
189             if (maxCount <= 0)
190             {
191                 throw new ArgumentOutOfRangeException("maxCount", maxCount, GetResourceString("SemaphoreSlim_ctor_MaxCountWrong"));
192             }
193
194             m_maxCount = maxCount;
195             m_lockObj = new object();
196             m_currentCount = initialCount;
197         }
198
199         #endregion
200
201         #region  Methods
202         /// <summary>
203         /// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>.
204         /// </summary>
205         /// <exception cref="T:System.ObjectDisposedException">The current instance has already been
206         /// disposed.</exception>
207         public void Wait()
208         {
209             // Call wait with infinite timeout
210             Wait(Timeout.Infinite, new CancellationToken());
211         }
212
213         /// <summary>
214         /// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>, while observing a
215         /// <see cref="T:System.Threading.CancellationToken"/>.
216         /// </summary>
217         /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> token to
218         /// observe.</param>
219         /// <exception cref="T:System.OperationCanceledException"><paramref name="cancellationToken"/> was
220         /// canceled.</exception>
221         /// <exception cref="T:System.ObjectDisposedException">The current instance has already been
222         /// disposed.</exception>
223         public void Wait(CancellationToken cancellationToken)
224         {
225             // Call wait with infinite timeout
226             Wait(Timeout.Infinite, cancellationToken);
227         }
228
229         /// <summary>
230         /// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>, using a <see
231         /// cref="T:System.TimeSpan"/> to measure the time interval.
232         /// </summary>
233         /// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds
234         /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
235         /// </param>
236         /// <returns>true if the current thread successfully entered the <see cref="SemaphoreSlim"/>;
237         /// otherwise, false.</returns>
238         /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative
239         /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater
240         /// than <see cref="System.Int32.MaxValue"/>.</exception>
241         public bool Wait(TimeSpan timeout)
242         {
243             // Validate the timeout
244             Int64 totalMilliseconds = (Int64)timeout.TotalMilliseconds;
245             if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue)
246             {
247                 throw new System.ArgumentOutOfRangeException(
248                     "timeout", timeout, GetResourceString("SemaphoreSlim_Wait_TimeoutWrong"));
249             }
250
251             // Call wait with the timeout milliseconds
252             return Wait((int)timeout.TotalMilliseconds, new CancellationToken());
253         }
254
255         /// <summary>
256         /// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>, using a <see
257         /// cref="T:System.TimeSpan"/> to measure the time interval, while observing a <see
258         /// cref="T:System.Threading.CancellationToken"/>.
259         /// </summary>
260         /// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds
261         /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
262         /// </param>
263         /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to
264         /// observe.</param>
265         /// <returns>true if the current thread successfully entered the <see cref="SemaphoreSlim"/>;
266         /// otherwise, false.</returns>
267         /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative
268         /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater
269         /// than <see cref="System.Int32.MaxValue"/>.</exception>
270         /// <exception cref="System.OperationCanceledException"><paramref name="cancellationToken"/> was canceled.</exception>
271         public bool Wait(TimeSpan timeout, CancellationToken cancellationToken)
272         {
273             // Validate the timeout
274             Int64 totalMilliseconds = (Int64)timeout.TotalMilliseconds;
275             if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue)
276             {
277                 throw new System.ArgumentOutOfRangeException(
278                     "timeout", timeout, GetResourceString("SemaphoreSlim_Wait_TimeoutWrong"));
279             }
280
281             // Call wait with the timeout milliseconds
282             return Wait((int)timeout.TotalMilliseconds, cancellationToken);
283         }
284
285         /// <summary>
286         /// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>, using a 32-bit
287         /// signed integer to measure the time interval.
288         /// </summary>
289         /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see
290         /// cref="Timeout.Infinite"/>(-1) to wait indefinitely.</param>
291         /// <returns>true if the current thread successfully entered the <see cref="SemaphoreSlim"/>;
292         /// otherwise, false.</returns>
293         /// <exception cref="ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a
294         /// negative number other than -1, which represents an infinite time-out.</exception>
295         public bool Wait(int millisecondsTimeout)
296         {
297             return Wait(millisecondsTimeout, new CancellationToken());
298         }
299
300
301         /// <summary>
302         /// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>,
303         /// using a 32-bit signed integer to measure the time interval, 
304         /// while observing a <see cref="T:System.Threading.CancellationToken"/>.
305         /// </summary>
306         /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see cref="Timeout.Infinite"/>(-1) to
307         /// wait indefinitely.</param>
308         /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to observe.</param>
309         /// <returns>true if the current thread successfully entered the <see cref="SemaphoreSlim"/>; otherwise, false.</returns>
310         /// <exception cref="ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a negative number other than -1,
311         /// which represents an infinite time-out.</exception>
312         /// <exception cref="System.OperationCanceledException"><paramref name="cancellationToken"/> was canceled.</exception>
313         public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
314         {
315             CheckDispose();
316
317             // Validate input
318             if (millisecondsTimeout < -1)
319             {
320                 throw new ArgumentOutOfRangeException(
321                     "totalMilliSeconds", millisecondsTimeout, GetResourceString("SemaphoreSlim_Wait_TimeoutWrong"));
322             }
323
324             cancellationToken.ThrowIfCancellationRequested();
325
326             uint startTime = 0;
327             if (millisecondsTimeout != Timeout.Infinite && millisecondsTimeout > 0)
328             {
329                 startTime = TimeoutHelper.GetTime();
330             }
331
332             bool waitSuccessful = false;
333             Task<bool> asyncWaitTask = null;
334             bool lockTaken = false;
335
336             //Register for cancellation outside of the main lock.
337             //NOTE: Register/deregister inside the lock can deadlock as different lock acquisition orders could
338             //      occur for (1)this.m_lockObj and (2)cts.internalLock
339             CancellationTokenRegistration cancellationTokenRegistration = cancellationToken.InternalRegisterWithoutEC(s_cancellationTokenCanceledEventHandler, this);
340             try
341             {
342                 // Perf: first spin wait for the count to be positive, but only up to the first planned yield.
343                 //       This additional amount of spinwaiting in addition
344                 //       to Monitor.Enter()’s spinwaiting has shown measurable perf gains in test scenarios.
345                 //
346                 SpinWait spin = new SpinWait();
347                 while (m_currentCount == 0 && !spin.NextSpinWillYield)
348                 {
349                     spin.SpinOnce();
350                 }
351                 // entering the lock and incrementing waiters must not suffer a thread-abort, else we cannot
352                 // clean up m_waitCount correctly, which may lead to deadlock due to non-woken waiters.
353                 try { }
354                 finally
355                 {
356                     Monitor.Enter(m_lockObj, ref lockTaken);
357                     if (lockTaken)
358                     {
359                         m_waitCount++;
360                     }
361                 }
362
363                 // If there are any async waiters, for fairness we'll get in line behind
364                 // then by translating our synchronous wait into an asynchronous one that we 
365                 // then block on (once we've released the lock).
366                 if (m_asyncHead != null)
367                 {
368                     Contract.Assert(m_asyncTail != null, "tail should not be null if head isn't");
369                     asyncWaitTask = WaitAsync(millisecondsTimeout, cancellationToken);
370                 }
371                     // There are no async waiters, so we can proceed with normal synchronous waiting.
372                 else
373                 {
374                     // If the count > 0 we are good to move on.
375                     // If not, then wait if we were given allowed some wait duration
376
377                     OperationCanceledException oce = null;
378
379                     if (m_currentCount == 0)
380                     {
381                         if (millisecondsTimeout == 0)
382                         {
383                             return false;
384                         }
385
386                         // Prepare for the main wait...
387                         // wait until the count become greater than zero or the timeout is expired
388                         try
389                         {
390                             waitSuccessful = WaitUntilCountOrTimeout(millisecondsTimeout, startTime, cancellationToken);
391                         }
392                         catch (OperationCanceledException e) { oce = e; }
393                     }
394
395                     // Now try to acquire.  We prioritize acquisition over cancellation/timeout so that we don't
396                     // lose any counts when there are asynchronous waiters in the mix.  Asynchronous waiters
397                     // defer to synchronous waiters in priority, which means that if it's possible an asynchronous
398                     // waiter didn't get released because a synchronous waiter was present, we need to ensure
399                     // that synchronous waiter succeeds so that they have a chance to release.
400                     Contract.Assert(!waitSuccessful || m_currentCount > 0, 
401                         "If the wait was successful, there should be count available.");
402                     if (m_currentCount > 0)
403                     {
404                         waitSuccessful = true;
405                         m_currentCount--;
406                     }
407                     else if (oce != null)
408                     {
409                         throw oce;
410                     }
411
412                     // Exposing wait handle which is lazily initialized if needed
413                     if (m_waitHandle != null && m_currentCount == 0)
414                     {
415                         m_waitHandle.Reset();
416                     }
417                 }
418             }
419             finally
420             {
421                 // Release the lock
422                 if (lockTaken)
423                 {
424                     m_waitCount--;
425                     Monitor.Exit(m_lockObj);
426                 }
427
428                 // Unregister the cancellation callback.
429                 cancellationTokenRegistration.Dispose();
430             }
431
432             // If we had to fall back to asynchronous waiting, block on it
433             // here now that we've released the lock, and return its
434             // result when available.  Otherwise, this was a synchronous
435             // wait, and whether we successfully acquired the semaphore is
436             // stored in waitSuccessful.
437
438             return (asyncWaitTask != null) ? asyncWaitTask.GetAwaiter().GetResult() : waitSuccessful;
439         }
440
441         /// <summary>
442         /// Local helper function, waits on the monitor until the monitor recieves signal or the
443         /// timeout is expired
444         /// </summary>
445         /// <param name="millisecondsTimeout">The maximum timeout</param>
446         /// <param name="startTime">The start ticks to calculate the elapsed time</param>
447         /// <param name="cancellationToken">The CancellationToken to observe.</param>
448         /// <returns>true if the monitor recieved a signal, false if the timeout expired</returns>
449         private bool WaitUntilCountOrTimeout(int millisecondsTimeout, uint startTime, CancellationToken cancellationToken)
450         {
451             int remainingWaitMilliseconds = Timeout.Infinite;
452
453             //Wait on the monitor as long as the count is zero
454             while (m_currentCount == 0)
455             {
456                 // If cancelled, we throw. Trying to wait could lead to deadlock.
457                 cancellationToken.ThrowIfCancellationRequested();
458
459                 if (millisecondsTimeout != Timeout.Infinite)
460                 {
461                     remainingWaitMilliseconds = TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout);
462                     if (remainingWaitMilliseconds <= 0)
463                     {
464                         // The thread has expires its timeout
465                         return false;
466                     }
467                 }
468                 // ** the actual wait **
469                 if (!Monitor.Wait(m_lockObj, remainingWaitMilliseconds))
470                 {
471                     return false;
472                 }
473             }
474
475             return true;
476         }
477
478         /// <summary>
479         /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/>.
480         /// </summary>
481         /// <returns>A task that will complete when the semaphore has been entered.</returns>
482         public Task WaitAsync()
483         {
484             return WaitAsync(Timeout.Infinite, default(CancellationToken));
485         }
486
487         /// <summary>
488         /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/>, while observing a
489         /// <see cref="T:System.Threading.CancellationToken"/>.
490         /// </summary>
491         /// <returns>A task that will complete when the semaphore has been entered.</returns>
492         /// <param name="cancellationToken">
493         /// The <see cref="T:System.Threading.CancellationToken"/> token to observe.
494         /// </param>
495         /// <exception cref="T:System.ObjectDisposedException">
496         /// The current instance has already been disposed.
497         /// </exception>
498         public Task WaitAsync(CancellationToken cancellationToken)
499         {
500             return WaitAsync(Timeout.Infinite, cancellationToken);
501         }
502
503         /// <summary>
504         /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/>,
505         /// using a 32-bit signed integer to measure the time interval.
506         /// </summary>
507         /// <param name="millisecondsTimeout">
508         /// The number of milliseconds to wait, or <see cref="Timeout.Infinite"/>(-1) to wait indefinitely.
509         /// </param>
510         /// <returns>
511         /// A task that will complete with a result of true if the current thread successfully entered 
512         /// the <see cref="SemaphoreSlim"/>, otherwise with a result of false.
513         /// </returns>
514         /// <exception cref="T:System.ObjectDisposedException">The current instance has already been
515         /// disposed.</exception>
516         /// <exception cref="ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a negative number other than -1,
517         /// which represents an infinite time-out.
518         /// </exception>
519         public Task<bool> WaitAsync(int millisecondsTimeout)
520         {
521             return WaitAsync(millisecondsTimeout, default(CancellationToken));
522         }
523
524         /// <summary>
525         /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/>, using a <see
526         /// cref="T:System.TimeSpan"/> to measure the time interval, while observing a
527         /// <see cref="T:System.Threading.CancellationToken"/>.
528         /// </summary>
529         /// <param name="timeout">
530         /// A <see cref="System.TimeSpan"/> that represents the number of milliseconds
531         /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
532         /// </param>
533         /// <param name="cancellationToken">
534         /// The <see cref="T:System.Threading.CancellationToken"/> token to observe.
535         /// </param>
536         /// <returns>
537         /// A task that will complete with a result of true if the current thread successfully entered 
538         /// the <see cref="SemaphoreSlim"/>, otherwise with a result of false.
539         /// </returns>
540         /// <exception cref="T:System.ObjectDisposedException">
541         /// The current instance has already been disposed.
542         /// </exception>
543         /// <exception cref="T:System.ArgumentOutOfRangeException">
544         /// <paramref name="timeout"/> is a negative number other than -1 milliseconds, which represents 
545         /// an infinite time-out -or- timeout is greater than <see cref="System.Int32.MaxValue"/>.
546         /// </exception>
547         public Task<bool> WaitAsync(TimeSpan timeout)
548         {
549             return WaitAsync(timeout, default(CancellationToken));
550         }
551
552         /// <summary>
553         /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/>, using a <see
554         /// cref="T:System.TimeSpan"/> to measure the time interval.
555         /// </summary>
556         /// <param name="timeout">
557         /// A <see cref="System.TimeSpan"/> that represents the number of milliseconds
558         /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
559         /// </param>
560         /// <returns>
561         /// A task that will complete with a result of true if the current thread successfully entered 
562         /// the <see cref="SemaphoreSlim"/>, otherwise with a result of false.
563         /// </returns>
564         /// <exception cref="T:System.ArgumentOutOfRangeException">
565         /// <paramref name="timeout"/> is a negative number other than -1 milliseconds, which represents 
566         /// an infinite time-out -or- timeout is greater than <see cref="System.Int32.MaxValue"/>.
567         /// </exception>
568         public Task<bool> WaitAsync(TimeSpan timeout, CancellationToken cancellationToken)
569         {
570             // Validate the timeout
571             Int64 totalMilliseconds = (Int64)timeout.TotalMilliseconds;
572             if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue)
573             {
574                 throw new System.ArgumentOutOfRangeException(
575                     "timeout", timeout, GetResourceString("SemaphoreSlim_Wait_TimeoutWrong"));
576             }
577
578             // Call wait with the timeout milliseconds
579             return WaitAsync((int)timeout.TotalMilliseconds, cancellationToken);
580         }
581
582         /// <summary>
583         /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/>,
584         /// using a 32-bit signed integer to measure the time interval, 
585         /// while observing a <see cref="T:System.Threading.CancellationToken"/>.
586         /// </summary>
587         /// <param name="millisecondsTimeout">
588         /// The number of milliseconds to wait, or <see cref="Timeout.Infinite"/>(-1) to wait indefinitely.
589         /// </param>
590         /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to observe.</param>
591         /// <returns>
592         /// A task that will complete with a result of true if the current thread successfully entered 
593         /// the <see cref="SemaphoreSlim"/>, otherwise with a result of false.
594         /// </returns>
595         /// <exception cref="T:System.ObjectDisposedException">The current instance has already been
596         /// disposed.</exception>
597         /// <exception cref="ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a negative number other than -1,
598         /// which represents an infinite time-out.
599         /// </exception>
600         public Task<bool> WaitAsync(int millisecondsTimeout, CancellationToken cancellationToken)
601         {
602             CheckDispose();
603
604             // Validate input
605             if (millisecondsTimeout < -1)
606             {
607                 throw new ArgumentOutOfRangeException(
608                     "totalMilliSeconds", millisecondsTimeout, GetResourceString("SemaphoreSlim_Wait_TimeoutWrong"));
609             }
610
611             // Bail early for cancellation
612             if (cancellationToken.IsCancellationRequested)
613                 return Task.FromCancellation<bool>(cancellationToken);
614
615             lock (m_lockObj)
616             {
617                 // If there are counts available, allow this waiter to succeed.
618                 if (m_currentCount > 0)
619                 {
620                     --m_currentCount;
621                     if (m_waitHandle != null && m_currentCount == 0) m_waitHandle.Reset();
622                     return s_trueTask;
623                 }
624                     // If there aren't, create and return a task to the caller.
625                     // The task will be completed either when they've successfully acquired
626                     // the semaphore or when the timeout expired or cancellation was requested.
627                 else
628                 {
629                     Contract.Assert(m_currentCount == 0, "m_currentCount should never be negative");
630                     var asyncWaiter = CreateAndAddAsyncWaiter();
631                     return (millisecondsTimeout == Timeout.Infinite && !cancellationToken.CanBeCanceled) ?
632                         asyncWaiter :
633                         WaitUntilCountOrTimeoutAsync(asyncWaiter, millisecondsTimeout, cancellationToken);
634                 }
635             }
636         }
637
638         /// <summary>Creates a new task and stores it into the async waiters list.</summary>
639         /// <returns>The created task.</returns>
640         private TaskNode CreateAndAddAsyncWaiter()
641         {
642             Contract.Assert(Monitor.IsEntered(m_lockObj), "Requires the lock be held");
643
644             // Create the task
645             var task = new TaskNode();
646
647             // Add it to the linked list
648             if (m_asyncHead == null)
649             {
650                 Contract.Assert(m_asyncTail == null, "If head is null, so too should be tail");
651                 m_asyncHead = task;
652                 m_asyncTail = task;
653             }
654             else
655             {
656                 Contract.Assert(m_asyncTail != null, "If head is not null, neither should be tail");
657                 m_asyncTail.Next = task;
658                 task.Prev = m_asyncTail;
659                 m_asyncTail = task;
660             }
661
662             // Hand it back
663             return task;
664         }
665
666         /// <summary>Removes the waiter task from the linked list.</summary>
667         /// <param name="task">The task to remove.</param>
668         /// <returns>true if the waiter was in the list; otherwise, false.</returns>
669         private bool RemoveAsyncWaiter(TaskNode task)
670         {
671             Contract.Requires(task != null, "Expected non-null task");
672             Contract.Assert(Monitor.IsEntered(m_lockObj), "Requires the lock be held");
673
674             // Is the task in the list?  To be in the list, either it's the head or it has a predecessor that's in the list.
675             bool wasInList = m_asyncHead == task || task.Prev != null;
676
677             // Remove it from the linked list
678             if (task.Next != null) task.Next.Prev = task.Prev;
679             if (task.Prev != null) task.Prev.Next = task.Next;
680             if (m_asyncHead == task) m_asyncHead = task.Next;
681             if (m_asyncTail == task) m_asyncTail = task.Prev;
682             Contract.Assert((m_asyncHead == null) == (m_asyncTail == null), "Head is null iff tail is null");
683
684             // Make sure not to leak
685             task.Next = task.Prev = null;
686
687             // Return whether the task was in the list
688             return wasInList;
689         }
690
691         /// <summary>Performs the asynchronous wait.</summary>
692         /// <param name="millisecondsTimeout">The timeout.</param>
693         /// <param name="cancellationToken">The cancellation token.</param>
694         /// <returns>The task to return to the caller.</returns>
695         private async Task<bool> WaitUntilCountOrTimeoutAsync(TaskNode asyncWaiter, int millisecondsTimeout, CancellationToken cancellationToken)
696         {
697             Contract.Assert(asyncWaiter != null, "Waiter should have been constructed");
698             Contract.Assert(Monitor.IsEntered(m_lockObj), "Requires the lock be held");
699
700             // Wait until either the task is completed, timeout occurs, or cancellation is requested.
701             // We need to ensure that the Task.Delay task is appropriately cleaned up if the await
702             // completes due to the asyncWaiter completing, so we use our own token that we can explicitly
703             // cancel, and we chain the caller's supplied token into it.
704             using (var cts = cancellationToken.CanBeCanceled ?
705                 CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, default(CancellationToken)) :
706                 new CancellationTokenSource())
707             {
708                 var waitCompleted = Task.WhenAny(asyncWaiter, Task.Delay(millisecondsTimeout, cts.Token));
709                 if (asyncWaiter == await waitCompleted.ConfigureAwait(false))
710                 {
711                     cts.Cancel(); // ensure that the Task.Delay task is cleaned up
712                     return true; // successfully acquired
713                 }
714             }
715
716             // If we get here, the wait has timed out or been canceled.
717
718             // If the await completed synchronously, we still hold the lock.  If it didn't,
719             // we no longer hold the lock.  As such, acquire it.
720             lock (m_lockObj)
721             {
722                 // Remove the task from the list.  If we're successful in doing so,
723                 // we know that no one else has tried to complete this waiter yet,
724                 // so we can safely cancel or timeout.
725                 if (RemoveAsyncWaiter(asyncWaiter))
726                 {
727                     cancellationToken.ThrowIfCancellationRequested(); // cancellation occurred
728                     return false; // timeout occurred
729                 }
730             }
731
732             // The waiter had already been removed, which means it's already completed or is about to
733             // complete, so let it, and don't return until it does.
734             return await asyncWaiter.ConfigureAwait(false);
735         }
736
737         /// <summary>
738         /// Exits the <see cref="SemaphoreSlim"/> once.
739         /// </summary>
740         /// <returns>The previous count of the <see cref="SemaphoreSlim"/>.</returns>
741         /// <exception cref="T:System.ObjectDisposedException">The current instance has already been
742         /// disposed.</exception>
743         public int Release()
744         {
745             return Release(1);
746         }
747
748         /// <summary>
749         /// Exits the <see cref="SemaphoreSlim"/> a specified number of times.
750         /// </summary>
751         /// <param name="releaseCount">The number of times to exit the semaphore.</param>
752         /// <returns>The previous count of the <see cref="SemaphoreSlim"/>.</returns>
753         /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="releaseCount"/> is less
754         /// than 1.</exception>
755         /// <exception cref="T:System.Threading.SemaphoreFullException">The <see cref="SemaphoreSlim"/> has
756         /// already reached its maximum size.</exception>
757         /// <exception cref="T:System.ObjectDisposedException">The current instance has already been
758         /// disposed.</exception>
759         public int Release(int releaseCount)
760         {
761             CheckDispose();
762
763             // Validate input
764             if (releaseCount < 1)
765             {
766                 throw new ArgumentOutOfRangeException(
767                     "releaseCount", releaseCount, GetResourceString("SemaphoreSlim_Release_CountWrong"));
768             }
769             int returnCount;
770
771             lock (m_lockObj)
772             {
773                 // Read the m_currentCount into a local variable to avoid unnecessary volatile accesses inside the lock.
774                 int currentCount = m_currentCount;
775                 returnCount = currentCount;
776
777                 // If the release count would result exceeding the maximum count, throw SemaphoreFullException.
778                 if (m_maxCount - currentCount < releaseCount)
779                 {
780                     throw new SemaphoreFullException();
781                 }
782
783                 // Increment the count by the actual release count
784                 currentCount += releaseCount;
785
786                 // Signal to any synchronous waiters
787                 int waitCount = m_waitCount;
788                 if (currentCount == 1 || waitCount == 1)
789                 {
790                     Monitor.Pulse(m_lockObj);
791                 }
792                 else if (waitCount > 1)
793                 {
794                     Monitor.PulseAll(m_lockObj);
795                 }
796
797                 // Now signal to any asynchronous waiters, if there are any.  While we've already
798                 // signaled the synchronous waiters, we still hold the lock, and thus
799                 // they won't have had an opportunity to acquire this yet.  So, when releasing
800                 // asynchronous waiters, we assume that all synchronous waiters will eventually
801                 // acquire the semaphore.  That could be a faulty assumption if those synchronous
802                 // waits are canceled, but the wait code path will handle that.
803                 if (m_asyncHead != null)
804                 {
805                     Contract.Assert(m_asyncTail != null, "tail should not be null if head isn't null");
806                     int maxAsyncToRelease = currentCount - waitCount;
807                     while (maxAsyncToRelease > 0 && m_asyncHead != null)
808                     {
809                         --currentCount;
810                         --maxAsyncToRelease;
811
812                         // Get the next async waiter to release and queue it to be completed
813                         var waiterTask = m_asyncHead;
814                         RemoveAsyncWaiter(waiterTask); // ensures waiterTask.Next/Prev are null
815                         QueueWaiterTask(waiterTask);
816                     }
817                 }
818                 m_currentCount = currentCount;
819
820                 // Exposing wait handle if it is not null
821                 if (m_waitHandle != null && returnCount == 0 && currentCount > 0)
822                 {
823                     m_waitHandle.Set();
824                 }
825             }
826
827             // And return the count
828             return returnCount;
829         }
830
831         /// <summary>
832         /// Queues a waiter task to the ThreadPool. We use this small helper method so that
833         /// the larger Release(count) method does not need to be SecuritySafeCritical.
834         /// </summary>
835         [SecuritySafeCritical] // for ThreadPool.UnsafeQueueCustomWorkItem
836         private static void QueueWaiterTask(TaskNode waiterTask)
837         {
838             ThreadPool.UnsafeQueueCustomWorkItem(waiterTask, forceGlobal: false);
839         }
840
841         /// <summary>
842         /// Releases all resources used by the current instance of <see
843         /// cref="SemaphoreSlim"/>.
844         /// </summary>
845         /// <remarks>
846         /// Unlike most of the members of <see cref="SemaphoreSlim"/>, <see cref="Dispose()"/> is not
847         /// thread-safe and may not be used concurrently with other members of this instance.
848         /// </remarks>
849         public void Dispose()
850         {
851             Dispose(true);
852             GC.SuppressFinalize(this);
853         }
854
855         /// <summary>
856         /// When overridden in a derived class, releases the unmanaged resources used by the 
857         /// <see cref="T:System.Threading.ManualResetEventSlim"/>, and optionally releases the managed resources.
858         /// </summary>
859         /// <param name="disposing">true to release both managed and unmanaged resources;
860         /// false to release only unmanaged resources.</param>
861         /// <remarks>
862         /// Unlike most of the members of <see cref="SemaphoreSlim"/>, <see cref="Dispose(Boolean)"/> is not
863         /// thread-safe and may not be used concurrently with other members of this instance.
864         /// </remarks>
865         protected virtual void Dispose(bool disposing)
866         {
867             if (disposing)
868             {
869                 if (m_waitHandle != null)
870                 {
871                     m_waitHandle.Close();
872                     m_waitHandle = null;
873                 }
874                 m_lockObj = null;
875                 m_asyncHead = null;
876                 m_asyncTail = null;
877             }
878         }
879
880
881         
882         /// <summary>
883         /// Private helper method to wake up waiters when a cancellationToken gets canceled.
884         /// </summary>
885         private static Action<object> s_cancellationTokenCanceledEventHandler = new Action<object>(CancellationTokenCanceledEventHandler);
886         private static void CancellationTokenCanceledEventHandler(object obj)
887         {
888             SemaphoreSlim semaphore = obj as SemaphoreSlim;
889             Contract.Assert(semaphore != null, "Expected a SemaphoreSlim");
890             lock (semaphore.m_lockObj)
891             {
892                 Monitor.PulseAll(semaphore.m_lockObj); //wake up all waiters.
893             }
894         }
895
896         /// <summary>
897         /// Checks the dispose status by checking the lock object, if it is null means that object
898         /// has been disposed and throw ObjectDisposedException
899         /// </summary>
900         private void CheckDispose()
901         {
902             if (m_lockObj == null)
903             {
904                 throw new ObjectDisposedException(null, GetResourceString("SemaphoreSlim_Disposed"));
905             }
906         }
907
908         /// <summary>
909         /// local helper function to retrieve the exception string message from the resource file
910         /// </summary>
911         /// <param name="str">The key string</param>
912         private static string GetResourceString(string str)
913         {
914             return Environment.GetResourceString(str);
915         }
916         #endregion
917     }
918 }