// ==++==
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// ==--==
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
//
// SemaphoreSlim.cs
//
// [....]
//
// A lightweight semahore class that contains the basic semaphore functions plus some useful functions like interrupt
// and wait handle exposing to allow waiting on multiple semaphores.
//
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Security.Permissions;
using System.Runtime.InteropServices;
using System.Diagnostics.Contracts;
// The class will be part of the current System.Threading namespace
namespace System.Threading
{
///
/// Limits the number of threads that can access a resource or pool of resources concurrently.
///
///
///
/// The provides a lightweight semaphore class that doesn't
/// use Windows kernel semaphores.
///
///
/// All public and protected members of are thread-safe and may be used
/// concurrently from multiple threads, with the exception of Dispose, which
/// must only be used when all other operations on the have
/// completed.
///
///
[ComVisible(false)]
[HostProtection(Synchronization = true, ExternalThreading = true)]
[DebuggerDisplay("Current Count = {m_currentCount}")]
public class SemaphoreSlim : IDisposable
{
#region Private Fields
// The semaphore count, initialized in the constructor to the initial value, every release call incremetns it
// and every wait call decrements it as long as its value is positive otherwise the wait will block.
// Its value must be between the maximum semaphore value and zero
private volatile int m_currentCount;
// The maximum semaphore value, it is initialized to Int.MaxValue if the client didn't specify it. it is used
// to check if the count excceeded the maxi value or not.
private readonly int m_maxCount;
// The number of waiting threads, it is set to zero in the constructor and increments before blocking the
// threading and decrements it back after that. It is used as flag for the release call to know if there are
// waiting threads in the monitor or not.
private volatile int m_waitCount;
// Dummy object used to in lock statements to protect the semaphore count, wait handle and cancelation
private object m_lockObj;
// Act as the semaphore wait handle, it's lazily initialized if needed, the first WaitHandle call initialize it
// and wait an release sets and resets it respectively as long as it is not null
private ManualResetEvent m_waitHandle;
// No maximum constant
private const int NO_MAXIMUM = Int32.MaxValue;
#endregion
#region Public properties
///
/// Gets the current count of the .
///
/// The current count of the .
public int CurrentCount
{
get { return m_currentCount; }
}
///
/// Returns a that can be used to wait on the semaphore.
///
/// A that can be used to wait on the
/// semaphore.
///
/// A successful wait on the does not imply a successful wait on
/// the itself, nor does it decrement the semaphore's
/// count. exists to allow a thread to block waiting on multiple
/// semaphores, but such a wait should be followed by a true wait on the target semaphore.
///
/// The has been disposed.
public WaitHandle AvailableWaitHandle
{
get
{
CheckDispose();
// Return it directly if it is not null
if (m_waitHandle != null)
return m_waitHandle;
//lock the count to avoid multiple threads initializing the handle if it is null
lock (m_lockObj)
{
if (m_waitHandle == null)
{
// The initial state for the wait handle is true if the count is greater than zero
// false otherwise
m_waitHandle = new ManualResetEvent(m_currentCount != 0);
}
}
return m_waitHandle;
}
}
#endregion
#region Constructors
///
/// Initializes a new instance of the class, specifying
/// the initial number of requests that can be granted concurrently.
///
/// The initial number of requests for the semaphore that can be granted
/// concurrently.
///
/// is less than 0.
public SemaphoreSlim(int initialCount)
: this(initialCount, NO_MAXIMUM)
{
}
///
/// Initializes a new instance of the class, specifying
/// the initial and maximum number of requests that can be granted concurrently.
///
/// The initial number of requests for the semaphore that can be granted
/// concurrently.
/// The maximum number of requests for the semaphore that can be granted
/// concurrently.
///
/// is less than 0. -or-
/// is greater than . -or-
/// is less than 0.
public SemaphoreSlim(int initialCount, int maxCount)
{
if (initialCount < 0 || initialCount > maxCount)
{
throw new ArgumentOutOfRangeException(
"initialCount", initialCount, GetResourceString("SemaphoreSlim_ctor_InitialCountWrong"));
}
//validate input
if (maxCount <= 0)
{
throw new ArgumentOutOfRangeException("maxCount", maxCount, GetResourceString("SemaphoreSlim_ctor_MaxCountWrong"));
}
m_maxCount = maxCount;
m_lockObj = new object();
m_currentCount = initialCount;
}
#endregion
#region Methods
///
/// Blocks the current thread until it can enter the .
///
/// The current instance has already been
/// disposed.
public void Wait()
{
// Call wait with infinite timeout
Wait(Timeout.Infinite, new CancellationToken());
}
///
/// Blocks the current thread until it can enter the , while observing a
/// .
///
/// The token to
/// observe.
/// was
/// canceled.
/// The current instance has already been
/// disposed.
public void Wait(CancellationToken cancellationToken)
{
// Call wait with infinite timeout
Wait(Timeout.Infinite, cancellationToken);
}
///
/// Blocks the current thread until it can enter the , using a to measure the time interval.
///
/// A that represents the number of milliseconds
/// to wait, or a that represents -1 milliseconds to wait indefinitely.
///
/// true if the current thread successfully entered the ;
/// otherwise, false.
/// is a negative
/// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater
/// than .
public bool Wait(TimeSpan timeout)
{
// Validate the timeout
Int64 totalMilliseconds = (Int64)timeout.TotalMilliseconds;
if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue)
{
throw new System.ArgumentOutOfRangeException(
"timeout", timeout, GetResourceString("SemaphoreSlim_Wait_TimeoutWrong"));
}
// Call wait with the timeout milliseconds
return Wait((int)timeout.TotalMilliseconds, new CancellationToken());
}
///
/// Blocks the current thread until it can enter the , using a to measure the time interval, while observing a .
///
/// A that represents the number of milliseconds
/// to wait, or a that represents -1 milliseconds to wait indefinitely.
///
/// The to
/// observe.
/// true if the current thread successfully entered the ;
/// otherwise, false.
/// is a negative
/// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater
/// than .
/// was canceled.
public bool Wait(TimeSpan timeout, CancellationToken cancellationToken)
{
// Validate the timeout
Int64 totalMilliseconds = (Int64)timeout.TotalMilliseconds;
if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue)
{
throw new System.ArgumentOutOfRangeException(
"timeout", timeout, GetResourceString("SemaphoreSlim_Wait_TimeoutWrong"));
}
// Call wait with the timeout milliseconds
return Wait((int)timeout.TotalMilliseconds, cancellationToken);
}
///
/// Blocks the current thread until it can enter the , using a 32-bit
/// signed integer to measure the time interval.
///
/// The number of milliseconds to wait, or (-1) to wait indefinitely.
/// true if the current thread successfully entered the ;
/// otherwise, false.
/// is a
/// negative number other than -1, which represents an infinite time-out.
public bool Wait(int millisecondsTimeout)
{
return Wait(millisecondsTimeout, new CancellationToken());
}
///
/// Blocks the current thread until it can enter the ,
/// using a 32-bit signed integer to measure the time interval,
/// while observing a .
///
/// The number of milliseconds to wait, or (-1) to
/// wait indefinitely.
/// The to observe.
/// true if the current thread successfully entered the ; otherwise, false.
/// is a negative number other than -1,
/// which represents an infinite time-out.
/// was canceled.
public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
{
CheckDispose();
// Validate input
if (millisecondsTimeout < -1)
{
throw new ArgumentOutOfRangeException(
"totalMilliSeconds", millisecondsTimeout, GetResourceString("SemaphoreSlim_Wait_TimeoutWrong"));
}
cancellationToken.ThrowIfCancellationRequested();
long startTimeTicks = 0;
if (millisecondsTimeout != Timeout.Infinite && millisecondsTimeout > 0)
{
startTimeTicks = DateTime.UtcNow.Ticks;
}
bool lockTaken = false;
//Register for cancellation outside of the main lock.
//NOTE: Register/deregister inside the lock can deadlock as different lock acquisition orders could
// occur for (1)this.m_lockObj and (2)cts.internalLock
CancellationTokenRegistration cancellationTokenRegistration = cancellationToken.Register(s_cancellationTokenCanceledEventHandler, this);
try
{
// Perf: first spin wait for the count to be positive, but only up to the first planned yield.
// This additional amount of spinwaiting in addition
// to Monitor.Enter()’s spinwaiting has shown measurable perf gains in test scenarios.
//
SpinWait spin = new SpinWait();
while (m_currentCount == 0 && !spin.NextSpinWillYield)
{
spin.SpinOnce();
}
// entering the lock and incrementing waiters must not suffer a thread-abort, else we cannot
// clean up m_waitCount correctly, which may lead to deadlock due to non-woken waiters.
try { }
finally
{
Monitor.Enter(m_lockObj, ref lockTaken);
if (lockTaken)
{
m_waitCount++;
}
}
// If the count > 0 we are good to move on.
// If not, then wait if we were given allowed some wait duration
if (m_currentCount == 0)
{
if (millisecondsTimeout == 0)
{
return false;
}
// Prepare for the main wait...
// wait until the count become greater than zero or the timeout is expired
if (!WaitUntilCountOrTimeout(millisecondsTimeout, startTimeTicks, cancellationToken))
{
return false;
}
}
// At this point the count should be greater than zero
Contract.Assert(m_currentCount > 0);
m_currentCount--;
// Exposing wait handle which is lazily initialized if needed
if (m_waitHandle != null && m_currentCount == 0)
{
m_waitHandle.Reset();
}
}
finally
{
// Release the lock
if (lockTaken)
{
m_waitCount--;
Monitor.Exit(m_lockObj);
}
// Unregister the cancellation callback.
cancellationTokenRegistration.Dispose();
}
return true;
}
///
/// Local helper function, waits on the monitor until the monitor recieves signal or the
/// timeout is expired
///
/// The maximum timeout
/// The start ticks to calculate the elapsed time
/// The CancellationToken to observe.
/// true if the monitor recieved a signal, false if the timeout expired
private bool WaitUntilCountOrTimeout(int millisecondsTimeout, long startTimeTicks, CancellationToken cancellationToken)
{
int remainingWaitMilliseconds = Timeout.Infinite;
//Wait on the monitor as long as the count is zero
while (m_currentCount == 0)
{
// If cancelled, we throw. Trying to wait could lead to deadlock.
cancellationToken.ThrowIfCancellationRequested();
if (millisecondsTimeout != Timeout.Infinite)
{
remainingWaitMilliseconds = UpdateTimeOut(startTimeTicks, millisecondsTimeout);
if (remainingWaitMilliseconds <= 0)
{
// The thread has expires its timeout
return false;
}
}
// ** the actual wait **
if (!Monitor.Wait(m_lockObj, remainingWaitMilliseconds))
{
return false;
}
}
return true;
}
///
/// Exits the once.
///
/// The previous count of the .
/// The current instance has already been
/// disposed.
public int Release()
{
return Release(1);
}
///
/// Exits the a specified number of times.
///
/// The number of times to exit the semaphore.
/// The previous count of the .
/// is less
/// than 1.
/// The has
/// already reached its maximum size.
/// The current instance has already been
/// disposed.
public int Release(int releaseCount)
{
CheckDispose();
// Validate input
if (releaseCount < 1)
{
throw new ArgumentOutOfRangeException(
"releaseCount", releaseCount, GetResourceString("SemaphoreSlim_Release_CountWrong"));
}
lock (m_lockObj)
{
// If thre release count would result exceeding the maximum count throw SemaphoreFullException
if (m_maxCount - m_currentCount < releaseCount)
{
throw new SemaphoreFullException();
}
// Increment the count by the actual release count
m_currentCount += releaseCount;
if (m_currentCount == 1 || m_waitCount == 1)
{
Monitor.Pulse(m_lockObj);
}
else if (m_waitCount > 1)
{
Monitor.PulseAll(m_lockObj);
}
// Exposing wait handle if it is not null
if (m_waitHandle != null && m_currentCount - releaseCount == 0)
{
m_waitHandle.Set();
}
return m_currentCount - releaseCount;
}
}
///
/// Releases all resources used by the current instance of .
///
///
/// Unlike most of the members of , is not
/// thread-safe and may not be used concurrently with other members of this instance.
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// When overridden in a derived class, releases the unmanaged resources used by the
/// , and optionally releases the managed resources.
///
/// true to release both managed and unmanaged resources;
/// false to release only unmanaged resources.
///
/// Unlike most of the members of , is not
/// thread-safe and may not be used concurrently with other members of this instance.
///
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (m_waitHandle != null)
{
m_waitHandle.Close();
m_waitHandle = null;
}
m_lockObj = null;
}
}
///
/// Helper function to measure and update the wait time
///
/// The first time (in Ticks) observed when the wait started
/// The orginal wait timeoutout in milliseconds
/// The new wait time in milliseconds, -1 if the time expired
private static int UpdateTimeOut(long startTimeTicks, int originalWaitMillisecondsTimeout)
{
// The function must be called in case the time out is not infinite
Contract.Assert(originalWaitMillisecondsTimeout != Timeout.Infinite);
long elapsedMilliseconds = (DateTime.UtcNow.Ticks - startTimeTicks) / TimeSpan.TicksPerMillisecond;
// Check the elapsed milliseconds is greater than max int because this property is long
if (elapsedMilliseconds > int.MaxValue)
{
return 0;
}
// Subtract the elapsed time from the current wait time
int currentWaitTimeout = originalWaitMillisecondsTimeout - (int)elapsedMilliseconds; ;
if (currentWaitTimeout <= 0)
{
return 0;
}
return currentWaitTimeout;
}
///
/// Private helper method to wake up waiters when a cancellationToken gets canceled.
///
private static Action