Introduction
Here is a C# implementation of a Dijkstra counting Semaphore. This can also be used as a Reverse Sensing Semaphore as a thread can block until the �Count� reaches zero. Any thread(s) waiting on WaitForStarvation()
will be Pulsed by the first thread that �Released� the count to zero. In addition, this has many helper methods and overloads normally not available in other implementations, such as: current Count, Acquire with timeouts, AquireAll()
, ReleaseAll()
, WaitForStarvation()
, and �Try� versions of the Acquire
and Release
methods.
The Code:
using System;
using System.Threading;
namespace SyncPrimitives
{
public sealed class Semaphore
{
#region Fields
private int count;
private int maxCount;
private readonly object syncLock;
private readonly object starvationLock;
#endregion
#region Constructors
public Semaphore(int maxCount) : this(maxCount, maxCount)
{
}
public Semaphore(int initialCount, int maxCount)
{
if ( initialCount < 0 )
throw new
ArgumentOutOfRangeException("initialCount must be >= 0.");
if ( maxCount < 1 )
throw new ArgumentOutOfRangeException("maxCount must be >= 1.");
if ( initialCount > maxCount)
throw new
ArgumentOutOfRangeException("initialCount" +
" must be <= maxCount.");
count = initialCount;
this.maxCount = maxCount;
syncLock = new object();
starvationLock = new object();
}
#endregion
#region Properties
public int Count
{
get
{
lock(syncLock)
{
return count;
}
}
}
public int MaxCount
{
get { return maxCount; }
}
#endregion
#region Public Methods
public bool Acquire()
{
return Acquire(Timeout.Infinite);
}
public bool Acquire(int millisecondsTimeout)
{
lock(syncLock)
{
while ( count == 0 )
try
{
if (!Monitor.Wait(syncLock, millisecondsTimeout))
return false;
}
catch
{
Monitor.Pulse(syncLock);
throw;
}
count--;
if ( count == 0 )
lock(starvationLock) { Monitor.PulseAll(starvationLock); }
return true;
}
}
public bool AcquireAll()
{
return AcquireAll(Timeout.Infinite);
}
public bool AcquireAll(int millisecondsTimeout)
{
int slotsGot = 0;
int elapsedMS = 0;
DateTime start = DateTime.Now;
int timeout = millisecondsTimeout;
for (int i = 0; i < maxCount; i++)
{
try
{
if (! Acquire(timeout) )
{
if ( slotsGot > 0 )
Release(slotsGot);
return false;
}
else
{
elapsedMS = (int)((TimeSpan)
(DateTime.Now - start)).TotalMilliseconds;
timeout = millisecondsTimeout - elapsedMS;
if ( timeout < 0 )
timeout = 0;
slotsGot++;
}
}
catch
{
if ( slotsGot > 0 )
Release(slotsGot);
throw;
}
}
lock(starvationLock) { Monitor.PulseAll(starvationLock); }
return true;
}
public void Release()
{
Release(1);
}
public void Release(int releaseCount)
{
if ( releaseCount < 1 )
throw new
ArgumentOutOfRangeException("releaseCount must be >= 1.");
lock(syncLock)
{
if ( (count + releaseCount) > maxCount )
throw new
ArgumentOutOfRangeException("releaseCount" +
" would cause the semaphore's count to exceed maxCount.");
count += releaseCount;
Monitor.PulseAll(syncLock);
}
}
public bool TryRelease()
{
return TryRelease(1);
}
public bool TryRelease(int releaseCount)
{
if ( releaseCount <= 0 )
return false;
lock(syncLock)
{
if ( (count + releaseCount) > maxCount )
return false;
else
count += releaseCount;
Monitor.PulseAll(syncLock);
return true;
}
}
public void ReleaseAll()
{
lock(syncLock)
{
count = maxCount;
Monitor.PulseAll(syncLock);
}
}
public void WaitForStarvation()
{
lock(starvationLock)
{
if ( Interlocked.CompareExchange(ref count, 0, 0) != 0 )
Monitor.Wait(starvationLock);
}
}
#endregion
}
}
History
- Version 1.1 - 06/20/04 - Changed
AcquireAll(ms)
to respect user's milliseconds total.