3 // Copyright (c) Microsoft Corporation. All rights reserved.
6 // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
10 // <OWNER>[....]</OWNER>
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.
15 // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
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;
27 // The class will be part of the current System.Threading namespace
28 namespace System.Threading
31 /// Limits the number of threads that can access a resource or pool of resources concurrently.
35 /// The <see cref="SemaphoreSlim"/> provides a lightweight semaphore class that doesn't
36 /// use Windows kernel semaphores.
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
46 [HostProtection(Synchronization = true, ExternalThreading = true)]
47 [DebuggerDisplay("Current Count = {m_currentCount}")]
48 public class SemaphoreSlim : IDisposable
50 #region Private Fields
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;
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;
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;
66 // Dummy object used to in lock statements to protect the semaphore count, wait handle and cancelation
67 private object m_lockObj;
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;
73 // Head of list representing asynchronous waits on the semaphore.
74 private TaskNode m_asyncHead;
76 // Tail of list representing asynchronous waits on the semaphore.
77 private TaskNode m_asyncTail;
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));
83 // No maximum constant
84 private const int NO_MAXIMUM = Int32.MaxValue;
86 // Task in a linked list of asynchronous waiters
87 private sealed class TaskNode : Task<bool>, IThreadPoolWorkItem
89 internal TaskNode Prev, Next;
90 internal TaskNode() : base() {}
93 void IThreadPoolWorkItem.ExecuteWorkItem()
95 bool setSuccessfully = TrySetResult(true);
96 Contract.Assert(setSuccessfully, "Should have been able to complete task");
100 void IThreadPoolWorkItem.MarkAborted(ThreadAbortException tae) { /* nop */ }
104 #region Public properties
107 /// Gets the current count of the <see cref="SemaphoreSlim"/>.
109 /// <value>The current count of the <see cref="SemaphoreSlim"/>.</value>
110 public int CurrentCount
112 get { return m_currentCount; }
116 /// Returns a <see cref="T:System.Threading.WaitHandle"/> that can be used to wait on the semaphore.
118 /// <value>A <see cref="T:System.Threading.WaitHandle"/> that can be used to wait on the
119 /// semaphore.</value>
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.
126 /// <exception cref="T:System.ObjectDisposedException">The <see
127 /// cref="SemaphoreSlim"/> has been disposed.</exception>
128 public WaitHandle AvailableWaitHandle
134 // Return it directly if it is not null
135 if (m_waitHandle != null)
138 //lock the count to avoid multiple threads initializing the handle if it is null
141 if (m_waitHandle == null)
143 // The initial state for the wait handle is true if the count is greater than zero
145 m_waitHandle = new ManualResetEvent(m_currentCount != 0);
156 /// Initializes a new instance of the <see cref="SemaphoreSlim"/> class, specifying
157 /// the initial number of requests that can be granted concurrently.
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)
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.
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)
182 if (initialCount < 0 || initialCount > maxCount)
184 throw new ArgumentOutOfRangeException(
185 "initialCount", initialCount, GetResourceString("SemaphoreSlim_ctor_InitialCountWrong"));
191 throw new ArgumentOutOfRangeException("maxCount", maxCount, GetResourceString("SemaphoreSlim_ctor_MaxCountWrong"));
194 m_maxCount = maxCount;
195 m_lockObj = new object();
196 m_currentCount = initialCount;
203 /// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>.
205 /// <exception cref="T:System.ObjectDisposedException">The current instance has already been
206 /// disposed.</exception>
209 // Call wait with infinite timeout
210 Wait(Timeout.Infinite, new CancellationToken());
214 /// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>, while observing a
215 /// <see cref="T:System.Threading.CancellationToken"/>.
217 /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> token to
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)
225 // Call wait with infinite timeout
226 Wait(Timeout.Infinite, cancellationToken);
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.
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.
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)
243 // Validate the timeout
244 Int64 totalMilliseconds = (Int64)timeout.TotalMilliseconds;
245 if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue)
247 throw new System.ArgumentOutOfRangeException(
248 "timeout", timeout, GetResourceString("SemaphoreSlim_Wait_TimeoutWrong"));
251 // Call wait with the timeout milliseconds
252 return Wait((int)timeout.TotalMilliseconds, new CancellationToken());
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"/>.
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.
263 /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to
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)
273 // Validate the timeout
274 Int64 totalMilliseconds = (Int64)timeout.TotalMilliseconds;
275 if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue)
277 throw new System.ArgumentOutOfRangeException(
278 "timeout", timeout, GetResourceString("SemaphoreSlim_Wait_TimeoutWrong"));
281 // Call wait with the timeout milliseconds
282 return Wait((int)timeout.TotalMilliseconds, cancellationToken);
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.
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)
297 return Wait(millisecondsTimeout, new CancellationToken());
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"/>.
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)
318 if (millisecondsTimeout < -1)
320 throw new ArgumentOutOfRangeException(
321 "totalMilliSeconds", millisecondsTimeout, GetResourceString("SemaphoreSlim_Wait_TimeoutWrong"));
324 cancellationToken.ThrowIfCancellationRequested();
327 if (millisecondsTimeout != Timeout.Infinite && millisecondsTimeout > 0)
329 startTime = TimeoutHelper.GetTime();
332 bool waitSuccessful = false;
333 Task<bool> asyncWaitTask = null;
334 bool lockTaken = false;
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);
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.
346 SpinWait spin = new SpinWait();
347 while (m_currentCount == 0 && !spin.NextSpinWillYield)
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.
356 Monitor.Enter(m_lockObj, ref lockTaken);
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)
368 Contract.Assert(m_asyncTail != null, "tail should not be null if head isn't");
369 asyncWaitTask = WaitAsync(millisecondsTimeout, cancellationToken);
371 // There are no async waiters, so we can proceed with normal synchronous waiting.
374 // If the count > 0 we are good to move on.
375 // If not, then wait if we were given allowed some wait duration
377 OperationCanceledException oce = null;
379 if (m_currentCount == 0)
381 if (millisecondsTimeout == 0)
386 // Prepare for the main wait...
387 // wait until the count become greater than zero or the timeout is expired
390 waitSuccessful = WaitUntilCountOrTimeout(millisecondsTimeout, startTime, cancellationToken);
392 catch (OperationCanceledException e) { oce = e; }
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)
404 waitSuccessful = true;
407 else if (oce != null)
412 // Exposing wait handle which is lazily initialized if needed
413 if (m_waitHandle != null && m_currentCount == 0)
415 m_waitHandle.Reset();
425 Monitor.Exit(m_lockObj);
428 // Unregister the cancellation callback.
429 cancellationTokenRegistration.Dispose();
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.
438 return (asyncWaitTask != null) ? asyncWaitTask.GetAwaiter().GetResult() : waitSuccessful;
442 /// Local helper function, waits on the monitor until the monitor recieves signal or the
443 /// timeout is expired
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)
451 int remainingWaitMilliseconds = Timeout.Infinite;
453 //Wait on the monitor as long as the count is zero
454 while (m_currentCount == 0)
456 // If cancelled, we throw. Trying to wait could lead to deadlock.
457 cancellationToken.ThrowIfCancellationRequested();
459 if (millisecondsTimeout != Timeout.Infinite)
461 remainingWaitMilliseconds = TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout);
462 if (remainingWaitMilliseconds <= 0)
464 // The thread has expires its timeout
468 // ** the actual wait **
469 if (!Monitor.Wait(m_lockObj, remainingWaitMilliseconds))
479 /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/>.
481 /// <returns>A task that will complete when the semaphore has been entered.</returns>
482 public Task WaitAsync()
484 return WaitAsync(Timeout.Infinite, default(CancellationToken));
488 /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/>, while observing a
489 /// <see cref="T:System.Threading.CancellationToken"/>.
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.
495 /// <exception cref="T:System.ObjectDisposedException">
496 /// The current instance has already been disposed.
498 public Task WaitAsync(CancellationToken cancellationToken)
500 return WaitAsync(Timeout.Infinite, cancellationToken);
504 /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/>,
505 /// using a 32-bit signed integer to measure the time interval.
507 /// <param name="millisecondsTimeout">
508 /// The number of milliseconds to wait, or <see cref="Timeout.Infinite"/>(-1) to wait indefinitely.
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.
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.
519 public Task<bool> WaitAsync(int millisecondsTimeout)
521 return WaitAsync(millisecondsTimeout, default(CancellationToken));
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"/>.
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.
533 /// <param name="cancellationToken">
534 /// The <see cref="T:System.Threading.CancellationToken"/> token to observe.
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.
540 /// <exception cref="T:System.ObjectDisposedException">
541 /// The current instance has already been disposed.
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"/>.
547 public Task<bool> WaitAsync(TimeSpan timeout)
549 return WaitAsync(timeout, default(CancellationToken));
553 /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/>, using a <see
554 /// cref="T:System.TimeSpan"/> to measure the time interval.
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.
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.
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"/>.
568 public Task<bool> WaitAsync(TimeSpan timeout, CancellationToken cancellationToken)
570 // Validate the timeout
571 Int64 totalMilliseconds = (Int64)timeout.TotalMilliseconds;
572 if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue)
574 throw new System.ArgumentOutOfRangeException(
575 "timeout", timeout, GetResourceString("SemaphoreSlim_Wait_TimeoutWrong"));
578 // Call wait with the timeout milliseconds
579 return WaitAsync((int)timeout.TotalMilliseconds, cancellationToken);
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"/>.
587 /// <param name="millisecondsTimeout">
588 /// The number of milliseconds to wait, or <see cref="Timeout.Infinite"/>(-1) to wait indefinitely.
590 /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to observe.</param>
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.
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.
600 public Task<bool> WaitAsync(int millisecondsTimeout, CancellationToken cancellationToken)
605 if (millisecondsTimeout < -1)
607 throw new ArgumentOutOfRangeException(
608 "totalMilliSeconds", millisecondsTimeout, GetResourceString("SemaphoreSlim_Wait_TimeoutWrong"));
611 // Bail early for cancellation
612 if (cancellationToken.IsCancellationRequested)
613 return Task.FromCancellation<bool>(cancellationToken);
617 // If there are counts available, allow this waiter to succeed.
618 if (m_currentCount > 0)
621 if (m_waitHandle != null && m_currentCount == 0) m_waitHandle.Reset();
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.
629 Contract.Assert(m_currentCount == 0, "m_currentCount should never be negative");
630 var asyncWaiter = CreateAndAddAsyncWaiter();
631 return (millisecondsTimeout == Timeout.Infinite && !cancellationToken.CanBeCanceled) ?
633 WaitUntilCountOrTimeoutAsync(asyncWaiter, millisecondsTimeout, cancellationToken);
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()
642 Contract.Assert(Monitor.IsEntered(m_lockObj), "Requires the lock be held");
645 var task = new TaskNode();
647 // Add it to the linked list
648 if (m_asyncHead == null)
650 Contract.Assert(m_asyncTail == null, "If head is null, so too should be tail");
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;
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)
671 Contract.Requires(task != null, "Expected non-null task");
672 Contract.Assert(Monitor.IsEntered(m_lockObj), "Requires the lock be held");
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;
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");
684 // Make sure not to leak
685 task.Next = task.Prev = null;
687 // Return whether the task was in the list
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)
697 Contract.Assert(asyncWaiter != null, "Waiter should have been constructed");
698 Contract.Assert(Monitor.IsEntered(m_lockObj), "Requires the lock be held");
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())
708 var waitCompleted = Task.WhenAny(asyncWaiter, Task.Delay(millisecondsTimeout, cts.Token));
709 if (asyncWaiter == await waitCompleted.ConfigureAwait(false))
711 cts.Cancel(); // ensure that the Task.Delay task is cleaned up
712 return true; // successfully acquired
716 // If we get here, the wait has timed out or been canceled.
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.
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))
727 cancellationToken.ThrowIfCancellationRequested(); // cancellation occurred
728 return false; // timeout occurred
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);
738 /// Exits the <see cref="SemaphoreSlim"/> once.
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>
749 /// Exits the <see cref="SemaphoreSlim"/> a specified number of times.
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)
764 if (releaseCount < 1)
766 throw new ArgumentOutOfRangeException(
767 "releaseCount", releaseCount, GetResourceString("SemaphoreSlim_Release_CountWrong"));
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;
777 // If the release count would result exceeding the maximum count, throw SemaphoreFullException.
778 if (m_maxCount - currentCount < releaseCount)
780 throw new SemaphoreFullException();
783 // Increment the count by the actual release count
784 currentCount += releaseCount;
786 // Signal to any synchronous waiters
787 int waitCount = m_waitCount;
788 if (currentCount == 1 || waitCount == 1)
790 Monitor.Pulse(m_lockObj);
792 else if (waitCount > 1)
794 Monitor.PulseAll(m_lockObj);
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)
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)
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);
818 m_currentCount = currentCount;
820 // Exposing wait handle if it is not null
821 if (m_waitHandle != null && returnCount == 0 && currentCount > 0)
827 // And return the count
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.
835 [SecuritySafeCritical] // for ThreadPool.UnsafeQueueCustomWorkItem
836 private static void QueueWaiterTask(TaskNode waiterTask)
838 ThreadPool.UnsafeQueueCustomWorkItem(waiterTask, forceGlobal: false);
842 /// Releases all resources used by the current instance of <see
843 /// cref="SemaphoreSlim"/>.
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.
849 public void Dispose()
852 GC.SuppressFinalize(this);
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.
859 /// <param name="disposing">true to release both managed and unmanaged resources;
860 /// false to release only unmanaged resources.</param>
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.
865 protected virtual void Dispose(bool disposing)
869 if (m_waitHandle != null)
871 m_waitHandle.Close();
883 /// Private helper method to wake up waiters when a cancellationToken gets canceled.
885 private static Action<object> s_cancellationTokenCanceledEventHandler = new Action<object>(CancellationTokenCanceledEventHandler);
886 private static void CancellationTokenCanceledEventHandler(object obj)
888 SemaphoreSlim semaphore = obj as SemaphoreSlim;
889 Contract.Assert(semaphore != null, "Expected a SemaphoreSlim");
890 lock (semaphore.m_lockObj)
892 Monitor.PulseAll(semaphore.m_lockObj); //wake up all waiters.
897 /// Checks the dispose status by checking the lock object, if it is null means that object
898 /// has been disposed and throw ObjectDisposedException
900 private void CheckDispose()
902 if (m_lockObj == null)
904 throw new ObjectDisposedException(null, GetResourceString("SemaphoreSlim_Disposed"));
909 /// local helper function to retrieve the exception string message from the resource file
911 /// <param name="str">The key string</param>
912 private static string GetResourceString(string str)
914 return Environment.GetResourceString(str);